fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
2779
modules/juce_graphics/native/juce_RenderingHelpers.h
Normal file
2779
modules/juce_graphics/native/juce_RenderingHelpers.h
Normal file
File diff suppressed because it is too large
Load Diff
401
modules/juce_graphics/native/juce_android_Fonts.cpp
Normal file
401
modules/juce_graphics/native/juce_android_Fonts.cpp
Normal file
@ -0,0 +1,401 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct DefaultFontNames
|
||||
{
|
||||
DefaultFontNames()
|
||||
: defaultSans ("sans"),
|
||||
defaultSerif ("serif"),
|
||||
defaultFixed ("monospace"),
|
||||
defaultFallback ("sans")
|
||||
{
|
||||
}
|
||||
|
||||
String getRealFontName (const String& faceName) const
|
||||
{
|
||||
if (faceName == Font::getDefaultSansSerifFontName()) return defaultSans;
|
||||
if (faceName == Font::getDefaultSerifFontName()) return defaultSerif;
|
||||
if (faceName == Font::getDefaultMonospacedFontName()) return defaultFixed;
|
||||
|
||||
return faceName;
|
||||
}
|
||||
|
||||
String defaultSans, defaultSerif, defaultFixed, defaultFallback;
|
||||
};
|
||||
|
||||
Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
|
||||
{
|
||||
static DefaultFontNames defaultNames;
|
||||
|
||||
Font f (font);
|
||||
f.setTypefaceName (defaultNames.getRealFontName (font.getTypefaceName()));
|
||||
return Typeface::createSystemTypefaceFor (f);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_USE_FREETYPE
|
||||
|
||||
StringArray FTTypefaceList::getDefaultFontDirectories()
|
||||
{
|
||||
return StringArray ("/system/fonts");
|
||||
}
|
||||
|
||||
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
|
||||
{
|
||||
return new FreeTypeTypeface (font);
|
||||
}
|
||||
|
||||
void Typeface::scanFolderForFonts (const File& folder)
|
||||
{
|
||||
FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName()));
|
||||
}
|
||||
|
||||
StringArray Font::findAllTypefaceNames()
|
||||
{
|
||||
return FTTypefaceList::getInstance()->findAllFamilyNames();
|
||||
}
|
||||
|
||||
StringArray Font::findAllTypefaceStyles (const String& family)
|
||||
{
|
||||
return FTTypefaceList::getInstance()->findAllTypefaceStyles (family);
|
||||
}
|
||||
|
||||
bool TextLayout::createNativeLayout (const AttributedString&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
//==============================================================================
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
STATICMETHOD (create, "create", "(Ljava/lang/String;I)Landroid/graphics/Typeface;") \
|
||||
STATICMETHOD (createFromFile, "createFromFile", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \
|
||||
|
||||
DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface");
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
//==============================================================================
|
||||
StringArray Font::findAllTypefaceNames()
|
||||
{
|
||||
StringArray results;
|
||||
|
||||
for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, "*.ttf"))
|
||||
results.addIfNotAlreadyThere (f.getFileNameWithoutExtension().upToLastOccurrenceOf ("-", false, false));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
StringArray Font::findAllTypefaceStyles (const String& family)
|
||||
{
|
||||
StringArray results ("Regular");
|
||||
|
||||
for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, family + "-*.ttf"))
|
||||
results.addIfNotAlreadyThere (f.getFileNameWithoutExtension().fromLastOccurrenceOf ("-", false, false));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
const float referenceFontSize = 256.0f;
|
||||
const float referenceFontToUnits = 1.0f / referenceFontSize;
|
||||
|
||||
//==============================================================================
|
||||
class AndroidTypeface : public Typeface
|
||||
{
|
||||
public:
|
||||
AndroidTypeface (const Font& font)
|
||||
: Typeface (font.getTypefaceName(), font.getTypefaceStyle()),
|
||||
ascent (0), descent (0), heightToPointsFactor (1.0f)
|
||||
{
|
||||
JNIEnv* const env = getEnv();
|
||||
|
||||
// First check whether there's an embedded asset with this font name:
|
||||
typeface = GlobalRef (android.activity.callObjectMethod (JuceAppActivity.getTypeFaceFromAsset,
|
||||
javaString ("fonts/" + name).get()));
|
||||
|
||||
if (typeface.get() == nullptr)
|
||||
{
|
||||
const bool isBold = style.contains ("Bold");
|
||||
const bool isItalic = style.contains ("Italic");
|
||||
|
||||
File fontFile (getFontFile (name, style));
|
||||
|
||||
if (! fontFile.exists())
|
||||
fontFile = findFontFile (name, isBold, isItalic);
|
||||
|
||||
if (fontFile.exists())
|
||||
typeface = GlobalRef (env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile,
|
||||
javaString (fontFile.getFullPathName()).get()));
|
||||
else
|
||||
typeface = GlobalRef (env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.create,
|
||||
javaString (getName()).get(),
|
||||
(isBold ? 1 : 0) + (isItalic ? 2 : 0)));
|
||||
}
|
||||
|
||||
initialise (env);
|
||||
}
|
||||
|
||||
AndroidTypeface (const void* data, size_t size)
|
||||
: Typeface (String (static_cast<uint64> (reinterpret_cast<uintptr_t> (data))), String())
|
||||
{
|
||||
JNIEnv* const env = getEnv();
|
||||
|
||||
LocalRef<jbyteArray> bytes (env->NewByteArray ((jsize) size));
|
||||
env->SetByteArrayRegion (bytes, 0, (jsize) size, (const jbyte*) data);
|
||||
|
||||
typeface = GlobalRef (android.activity.callObjectMethod (JuceAppActivity.getTypeFaceFromByteArray, bytes.get()));
|
||||
|
||||
initialise (env);
|
||||
}
|
||||
|
||||
void initialise (JNIEnv* const env)
|
||||
{
|
||||
rect = GlobalRef (env->NewObject (AndroidRect, AndroidRect.constructor, 0, 0, 0, 0));
|
||||
|
||||
paint = GlobalRef (GraphicsHelpers::createPaint (Graphics::highResamplingQuality));
|
||||
const LocalRef<jobject> ignored (paint.callObjectMethod (AndroidPaint.setTypeface, typeface.get()));
|
||||
|
||||
paint.callVoidMethod (AndroidPaint.setTextSize, referenceFontSize);
|
||||
|
||||
const float fullAscent = std::abs (paint.callFloatMethod (AndroidPaint.ascent));
|
||||
const float fullDescent = paint.callFloatMethod (AndroidPaint.descent);
|
||||
const float totalHeight = fullAscent + fullDescent;
|
||||
|
||||
ascent = fullAscent / totalHeight;
|
||||
descent = fullDescent / totalHeight;
|
||||
heightToPointsFactor = referenceFontSize / totalHeight;
|
||||
}
|
||||
|
||||
float getAscent() const override { return ascent; }
|
||||
float getDescent() const override { return descent; }
|
||||
float getHeightToPointsFactor() const override { return heightToPointsFactor; }
|
||||
|
||||
float getStringWidth (const String& text) override
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
const int numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());
|
||||
jfloatArray widths = env->NewFloatArray (numChars);
|
||||
|
||||
const int numDone = paint.callIntMethod (AndroidPaint.getTextWidths, javaString (text).get(), widths);
|
||||
|
||||
HeapBlock<jfloat> localWidths (static_cast<size_t> (numDone));
|
||||
env->GetFloatArrayRegion (widths, 0, numDone, localWidths);
|
||||
env->DeleteLocalRef (widths);
|
||||
|
||||
float x = 0;
|
||||
for (int i = 0; i < numDone; ++i)
|
||||
x += localWidths[i];
|
||||
|
||||
return x * referenceFontToUnits;
|
||||
}
|
||||
|
||||
void getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) override
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
const int numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());
|
||||
jfloatArray widths = env->NewFloatArray (numChars);
|
||||
|
||||
const int numDone = paint.callIntMethod (AndroidPaint.getTextWidths, javaString (text).get(), widths);
|
||||
|
||||
HeapBlock<jfloat> localWidths (static_cast<size_t> (numDone));
|
||||
env->GetFloatArrayRegion (widths, 0, numDone, localWidths);
|
||||
env->DeleteLocalRef (widths);
|
||||
|
||||
auto s = text.getCharPointer();
|
||||
|
||||
xOffsets.add (0);
|
||||
|
||||
float x = 0;
|
||||
for (int i = 0; i < numDone; ++i)
|
||||
{
|
||||
const float local = localWidths[i];
|
||||
|
||||
// Android uses jchar (UTF-16) characters
|
||||
jchar ch = (jchar) s.getAndAdvance();
|
||||
|
||||
// Android has no proper glyph support, so we have to do
|
||||
// a hacky workaround for ligature detection
|
||||
|
||||
#if JUCE_STRING_UTF_TYPE <= 16
|
||||
static_assert (sizeof (int) >= (sizeof (jchar) * 2), "Unable store two java chars in one glyph");
|
||||
|
||||
// if the width of this glyph is zero inside the string but has
|
||||
// a width on it's own, then it's probably due to ligature
|
||||
if (local == 0.0f && glyphs.size() > 0 && getStringWidth (String (ch)) > 0.0f)
|
||||
{
|
||||
// modify the previous glyph
|
||||
int& glyphNumber = glyphs.getReference (glyphs.size() - 1);
|
||||
|
||||
// make sure this is not a three character ligature
|
||||
if (glyphNumber < std::numeric_limits<jchar>::max())
|
||||
{
|
||||
const unsigned int previousGlyph
|
||||
= static_cast<unsigned int> (glyphNumber) & ((1U << (sizeof (jchar) * 8U)) - 1U);
|
||||
const unsigned int thisGlyph
|
||||
= static_cast<unsigned int> (ch) & ((1U << (sizeof (jchar) * 8U)) - 1U);
|
||||
|
||||
glyphNumber = static_cast<int> ((thisGlyph << (sizeof (jchar) * 8U)) | previousGlyph);
|
||||
ch = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
glyphs.add ((int) ch);
|
||||
x += local;
|
||||
xOffsets.add (x * referenceFontToUnits);
|
||||
}
|
||||
}
|
||||
|
||||
bool getOutlineForGlyph (int /*glyphNumber*/, Path& /*destPath*/) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& t, float /*fontHeight*/) override
|
||||
{
|
||||
#if JUCE_STRING_UTF_TYPE <= 16
|
||||
static_assert (sizeof (int) >= (sizeof (jchar) * 2), "Unable store two jni chars in one int");
|
||||
|
||||
// glyphNumber of zero is used to indicate that the last character was a ligature
|
||||
if (glyphNumber == 0) return nullptr;
|
||||
|
||||
jchar ch1 = (static_cast<unsigned int> (glyphNumber) >> 0) & ((1U << (sizeof (jchar) * 8U)) - 1U);
|
||||
jchar ch2 = (static_cast<unsigned int> (glyphNumber) >> (sizeof (jchar) * 8U)) & ((1U << (sizeof (jchar) * 8U)) - 1U);
|
||||
#else
|
||||
jchar ch1 = glyphNumber, ch2 = 0;
|
||||
#endif
|
||||
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
jobject matrix = GraphicsHelpers::createMatrix (env, AffineTransform::scale (referenceFontToUnits).followedBy (t));
|
||||
jintArray maskData = (jintArray) android.activity.callObjectMethod (JuceAppActivity.renderGlyph, ch1, ch2, paint.get(), matrix, rect.get());
|
||||
|
||||
env->DeleteLocalRef (matrix);
|
||||
|
||||
const int left = env->GetIntField (rect.get(), AndroidRect.left);
|
||||
const int top = env->GetIntField (rect.get(), AndroidRect.top);
|
||||
const int right = env->GetIntField (rect.get(), AndroidRect.right);
|
||||
const int bottom = env->GetIntField (rect.get(), AndroidRect.bottom);
|
||||
|
||||
const Rectangle<int> bounds (left, top, right - left, bottom - top);
|
||||
|
||||
EdgeTable* et = nullptr;
|
||||
|
||||
if (! bounds.isEmpty())
|
||||
{
|
||||
et = new EdgeTable (bounds);
|
||||
|
||||
jint* const maskDataElements = env->GetIntArrayElements (maskData, 0);
|
||||
const jint* mask = maskDataElements;
|
||||
|
||||
for (int y = top; y < bottom; ++y)
|
||||
{
|
||||
#if JUCE_LITTLE_ENDIAN
|
||||
const uint8* const lineBytes = ((const uint8*) mask) + 3;
|
||||
#else
|
||||
const uint8* const lineBytes = (const uint8*) mask;
|
||||
#endif
|
||||
|
||||
et->clipLineToMask (left, y, lineBytes, 4, bounds.getWidth());
|
||||
mask += bounds.getWidth();
|
||||
}
|
||||
|
||||
env->ReleaseIntArrayElements (maskData, maskDataElements, 0);
|
||||
}
|
||||
|
||||
env->DeleteLocalRef (maskData);
|
||||
return et;
|
||||
}
|
||||
|
||||
GlobalRef typeface, paint, rect;
|
||||
float ascent, descent, heightToPointsFactor;
|
||||
|
||||
private:
|
||||
static File findFontFile (const String& family,
|
||||
const bool bold, const bool italic)
|
||||
{
|
||||
File file;
|
||||
|
||||
if (bold || italic)
|
||||
{
|
||||
String suffix;
|
||||
if (bold) suffix = "Bold";
|
||||
if (italic) suffix << "Italic";
|
||||
|
||||
file = getFontFile (family, suffix);
|
||||
|
||||
if (file.exists())
|
||||
return file;
|
||||
}
|
||||
|
||||
file = getFontFile (family, "Regular");
|
||||
|
||||
if (! file.exists())
|
||||
file = getFontFile (family, String());
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
static File getFontFile (const String& family, const String& style)
|
||||
{
|
||||
String path ("/system/fonts/" + family);
|
||||
|
||||
if (style.isNotEmpty())
|
||||
path << '-' << style;
|
||||
|
||||
return File (path + ".ttf");
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidTypeface)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
|
||||
{
|
||||
return new AndroidTypeface (font);
|
||||
}
|
||||
|
||||
Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size)
|
||||
{
|
||||
return new AndroidTypeface (data, size);
|
||||
}
|
||||
|
||||
void Typeface::scanFolderForFonts (const File&)
|
||||
{
|
||||
jassertfalse; // not available unless using FreeType
|
||||
}
|
||||
|
||||
bool TextLayout::createNativeLayout (const AttributedString&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace GraphicsHelpers
|
||||
{
|
||||
jobject createPaint (Graphics::ResamplingQuality quality)
|
||||
{
|
||||
jint constructorFlags = 1 /*ANTI_ALIAS_FLAG*/
|
||||
| 4 /*DITHER_FLAG*/
|
||||
| 128 /*SUBPIXEL_TEXT_FLAG*/;
|
||||
|
||||
if (quality > Graphics::lowResamplingQuality)
|
||||
constructorFlags |= 2; /*FILTER_BITMAP_FLAG*/
|
||||
|
||||
return getEnv()->NewObject (AndroidPaint, AndroidPaint.constructor, constructorFlags);
|
||||
}
|
||||
|
||||
const jobject createMatrix (JNIEnv* env, const AffineTransform& t)
|
||||
{
|
||||
jobject m = env->NewObject (AndroidMatrix, AndroidMatrix.constructor);
|
||||
|
||||
jfloat values[9] = { t.mat00, t.mat01, t.mat02,
|
||||
t.mat10, t.mat11, t.mat12,
|
||||
0.0f, 0.0f, 1.0f };
|
||||
|
||||
jfloatArray javaArray = env->NewFloatArray (9);
|
||||
env->SetFloatArrayRegion (javaArray, 0, 9, values);
|
||||
|
||||
env->CallVoidMethod (m, AndroidMatrix.setValues, javaArray);
|
||||
env->DeleteLocalRef (javaArray);
|
||||
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const
|
||||
{
|
||||
return SoftwareImageType().create (format, width, height, clearImage);
|
||||
}
|
||||
|
||||
} // namespace juce
|
30
modules/juce_graphics/native/juce_android_IconHelpers.cpp
Normal file
30
modules/juce_graphics/native/juce_android_IconHelpers.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
Image JUCE_API getIconFromApplication (const String&, int) { return {}; }
|
||||
}
|
462
modules/juce_graphics/native/juce_freetype_Fonts.cpp
Normal file
462
modules/juce_graphics/native/juce_freetype_Fonts.cpp
Normal file
@ -0,0 +1,462 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct FTLibWrapper : public ReferenceCountedObject
|
||||
{
|
||||
FTLibWrapper() : library (0)
|
||||
{
|
||||
if (FT_Init_FreeType (&library) != 0)
|
||||
{
|
||||
library = 0;
|
||||
DBG ("Failed to initialize FreeType");
|
||||
}
|
||||
}
|
||||
|
||||
~FTLibWrapper()
|
||||
{
|
||||
if (library != 0)
|
||||
FT_Done_FreeType (library);
|
||||
}
|
||||
|
||||
FT_Library library;
|
||||
|
||||
using Ptr = ReferenceCountedObjectPtr<FTLibWrapper>;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTLibWrapper)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct FTFaceWrapper : public ReferenceCountedObject
|
||||
{
|
||||
FTFaceWrapper (const FTLibWrapper::Ptr& ftLib, const File& file, int faceIndex)
|
||||
: face (0), library (ftLib)
|
||||
{
|
||||
if (FT_New_Face (ftLib->library, file.getFullPathName().toUTF8(), faceIndex, &face) != 0)
|
||||
face = 0;
|
||||
}
|
||||
|
||||
FTFaceWrapper (const FTLibWrapper::Ptr& ftLib, const void* data, size_t dataSize, int faceIndex)
|
||||
: face (0), library (ftLib), savedFaceData (data, dataSize)
|
||||
{
|
||||
if (FT_New_Memory_Face (ftLib->library, (const FT_Byte*) savedFaceData.getData(),
|
||||
(FT_Long) savedFaceData.getSize(), faceIndex, &face) != 0)
|
||||
face = 0;
|
||||
}
|
||||
|
||||
~FTFaceWrapper()
|
||||
{
|
||||
if (face != 0)
|
||||
FT_Done_Face (face);
|
||||
}
|
||||
|
||||
FT_Face face;
|
||||
FTLibWrapper::Ptr library;
|
||||
MemoryBlock savedFaceData;
|
||||
|
||||
using Ptr = ReferenceCountedObjectPtr<FTFaceWrapper>;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTFaceWrapper)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class FTTypefaceList : private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
FTTypefaceList() : library (new FTLibWrapper())
|
||||
{
|
||||
scanFontPaths (getDefaultFontDirectories());
|
||||
}
|
||||
|
||||
~FTTypefaceList()
|
||||
{
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct KnownTypeface
|
||||
{
|
||||
KnownTypeface (const File& f, const int index, const FTFaceWrapper& face)
|
||||
: file (f),
|
||||
family (face.face->family_name),
|
||||
style (face.face->style_name),
|
||||
faceIndex (index),
|
||||
isMonospaced ((face.face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) != 0),
|
||||
isSansSerif (isFaceSansSerif (family))
|
||||
{
|
||||
}
|
||||
|
||||
const File file;
|
||||
const String family, style;
|
||||
const int faceIndex;
|
||||
const bool isMonospaced, isSansSerif;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownTypeface)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static FTFaceWrapper::Ptr selectUnicodeCharmap (FTFaceWrapper* face)
|
||||
{
|
||||
if (face != nullptr)
|
||||
if (FT_Select_Charmap (face->face, ft_encoding_unicode) != 0)
|
||||
FT_Set_Charmap (face->face, face->face->charmaps[0]);
|
||||
|
||||
return face;
|
||||
}
|
||||
|
||||
FTFaceWrapper::Ptr createFace (const void* data, size_t dataSize, int index)
|
||||
{
|
||||
return selectUnicodeCharmap (new FTFaceWrapper (library, data, dataSize, index));
|
||||
}
|
||||
|
||||
FTFaceWrapper::Ptr createFace (const File& file, int index)
|
||||
{
|
||||
return selectUnicodeCharmap (new FTFaceWrapper (library, file, index));
|
||||
}
|
||||
|
||||
FTFaceWrapper::Ptr createFace (const String& fontName, const String& fontStyle)
|
||||
{
|
||||
const KnownTypeface* ftFace = matchTypeface (fontName, fontStyle);
|
||||
|
||||
if (ftFace == nullptr) ftFace = matchTypeface (fontName, "Regular");
|
||||
if (ftFace == nullptr) ftFace = matchTypeface (fontName, String());
|
||||
|
||||
if (ftFace != nullptr)
|
||||
return createFace (ftFace->file, ftFace->faceIndex);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
StringArray findAllFamilyNames() const
|
||||
{
|
||||
StringArray s;
|
||||
|
||||
for (int i = 0; i < faces.size(); ++i)
|
||||
s.addIfNotAlreadyThere (faces.getUnchecked(i)->family);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static int indexOfRegularStyle (const StringArray& styles)
|
||||
{
|
||||
int i = styles.indexOf ("Regular", true);
|
||||
|
||||
if (i < 0)
|
||||
for (i = 0; i < styles.size(); ++i)
|
||||
if (! (styles[i].containsIgnoreCase ("Bold") || styles[i].containsIgnoreCase ("Italic")))
|
||||
break;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
StringArray findAllTypefaceStyles (const String& family) const
|
||||
{
|
||||
StringArray s;
|
||||
|
||||
for (int i = 0; i < faces.size(); ++i)
|
||||
{
|
||||
const KnownTypeface* const face = faces.getUnchecked(i);
|
||||
|
||||
if (face->family == family)
|
||||
s.addIfNotAlreadyThere (face->style);
|
||||
}
|
||||
|
||||
// try to get a regular style to be first in the list
|
||||
const int regular = indexOfRegularStyle (s);
|
||||
if (regular > 0)
|
||||
s.strings.swap (0, regular);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void scanFontPaths (const StringArray& paths)
|
||||
{
|
||||
for (int i = 0; i < paths.size(); ++i)
|
||||
{
|
||||
DirectoryIterator iter (File::getCurrentWorkingDirectory()
|
||||
.getChildFile (paths[i]), true);
|
||||
|
||||
while (iter.next())
|
||||
if (iter.getFile().hasFileExtension ("ttf;pfb;pcf;otf"))
|
||||
scanFont (iter.getFile());
|
||||
}
|
||||
}
|
||||
|
||||
void getMonospacedNames (StringArray& monoSpaced) const
|
||||
{
|
||||
for (int i = 0; i < faces.size(); ++i)
|
||||
if (faces.getUnchecked(i)->isMonospaced)
|
||||
monoSpaced.addIfNotAlreadyThere (faces.getUnchecked(i)->family);
|
||||
}
|
||||
|
||||
void getSerifNames (StringArray& serif) const
|
||||
{
|
||||
for (int i = 0; i < faces.size(); ++i)
|
||||
if (! faces.getUnchecked(i)->isSansSerif)
|
||||
serif.addIfNotAlreadyThere (faces.getUnchecked(i)->family);
|
||||
}
|
||||
|
||||
void getSansSerifNames (StringArray& sansSerif) const
|
||||
{
|
||||
for (int i = 0; i < faces.size(); ++i)
|
||||
if (faces.getUnchecked(i)->isSansSerif)
|
||||
sansSerif.addIfNotAlreadyThere (faces.getUnchecked(i)->family);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (FTTypefaceList)
|
||||
|
||||
private:
|
||||
FTLibWrapper::Ptr library;
|
||||
OwnedArray<KnownTypeface> faces;
|
||||
|
||||
static StringArray getDefaultFontDirectories();
|
||||
|
||||
void scanFont (const File& file)
|
||||
{
|
||||
int faceIndex = 0;
|
||||
int numFaces = 0;
|
||||
|
||||
do
|
||||
{
|
||||
FTFaceWrapper face (library, file, faceIndex);
|
||||
|
||||
if (face.face != 0)
|
||||
{
|
||||
if (faceIndex == 0)
|
||||
numFaces = (int) face.face->num_faces;
|
||||
|
||||
if ((face.face->face_flags & FT_FACE_FLAG_SCALABLE) != 0)
|
||||
faces.add (new KnownTypeface (file, faceIndex, face));
|
||||
}
|
||||
|
||||
++faceIndex;
|
||||
}
|
||||
while (faceIndex < numFaces);
|
||||
}
|
||||
|
||||
const KnownTypeface* matchTypeface (const String& familyName, const String& style) const noexcept
|
||||
{
|
||||
for (int i = 0; i < faces.size(); ++i)
|
||||
{
|
||||
const KnownTypeface* const face = faces.getUnchecked(i);
|
||||
|
||||
if (face->family == familyName
|
||||
&& (face->style.equalsIgnoreCase (style) || style.isEmpty()))
|
||||
return face;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool isFaceSansSerif (const String& family)
|
||||
{
|
||||
static const char* sansNames[] = { "Sans", "Verdana", "Arial", "Ubuntu" };
|
||||
|
||||
for (int i = 0; i < numElementsInArray (sansNames); ++i)
|
||||
if (family.containsIgnoreCase (sansNames[i]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTTypefaceList)
|
||||
};
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (FTTypefaceList)
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class FreeTypeTypeface : public CustomTypeface
|
||||
{
|
||||
public:
|
||||
FreeTypeTypeface (const Font& font)
|
||||
: faceWrapper (FTTypefaceList::getInstance()->createFace (font.getTypefaceName(),
|
||||
font.getTypefaceStyle()))
|
||||
{
|
||||
if (faceWrapper != nullptr)
|
||||
initialiseCharacteristics (font.getTypefaceName(),
|
||||
font.getTypefaceStyle());
|
||||
}
|
||||
|
||||
FreeTypeTypeface (const void* data, size_t dataSize)
|
||||
: faceWrapper (FTTypefaceList::getInstance()->createFace (data, dataSize, 0))
|
||||
{
|
||||
if (faceWrapper != nullptr)
|
||||
initialiseCharacteristics (faceWrapper->face->family_name,
|
||||
faceWrapper->face->style_name);
|
||||
}
|
||||
|
||||
void initialiseCharacteristics (const String& fontName, const String& fontStyle)
|
||||
{
|
||||
setCharacteristics (fontName, fontStyle,
|
||||
faceWrapper->face->ascender / (float) (faceWrapper->face->ascender - faceWrapper->face->descender),
|
||||
L' ');
|
||||
}
|
||||
|
||||
bool loadGlyphIfPossible (const juce_wchar character)
|
||||
{
|
||||
if (faceWrapper != nullptr)
|
||||
{
|
||||
FT_Face face = faceWrapper->face;
|
||||
const unsigned int glyphIndex = FT_Get_Char_Index (face, (FT_ULong) character);
|
||||
|
||||
if (FT_Load_Glyph (face, glyphIndex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM | FT_LOAD_NO_HINTING) == 0
|
||||
&& face->glyph->format == ft_glyph_format_outline)
|
||||
{
|
||||
const float scale = 1.0f / (float) (face->ascender - face->descender);
|
||||
Path destShape;
|
||||
|
||||
if (getGlyphShape (destShape, face->glyph->outline, scale))
|
||||
{
|
||||
addGlyph (character, destShape, face->glyph->metrics.horiAdvance * scale);
|
||||
|
||||
if ((face->face_flags & FT_FACE_FLAG_KERNING) != 0)
|
||||
addKerning (face, (uint32) character, glyphIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
FTFaceWrapper::Ptr faceWrapper;
|
||||
|
||||
bool getGlyphShape (Path& destShape, const FT_Outline& outline, const float scaleX)
|
||||
{
|
||||
const float scaleY = -scaleX;
|
||||
const short* const contours = outline.contours;
|
||||
const char* const tags = outline.tags;
|
||||
const FT_Vector* const points = outline.points;
|
||||
|
||||
for (int c = 0; c < outline.n_contours; ++c)
|
||||
{
|
||||
const int startPoint = (c == 0) ? 0 : contours [c - 1] + 1;
|
||||
const int endPoint = contours[c];
|
||||
|
||||
for (int p = startPoint; p <= endPoint; ++p)
|
||||
{
|
||||
const float x = scaleX * points[p].x;
|
||||
const float y = scaleY * points[p].y;
|
||||
|
||||
if (p == startPoint)
|
||||
{
|
||||
if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Conic)
|
||||
{
|
||||
float x2 = scaleX * points [endPoint].x;
|
||||
float y2 = scaleY * points [endPoint].y;
|
||||
|
||||
if (FT_CURVE_TAG (tags[endPoint]) != FT_Curve_Tag_On)
|
||||
{
|
||||
x2 = (x + x2) * 0.5f;
|
||||
y2 = (y + y2) * 0.5f;
|
||||
}
|
||||
|
||||
destShape.startNewSubPath (x2, y2);
|
||||
}
|
||||
else
|
||||
{
|
||||
destShape.startNewSubPath (x, y);
|
||||
}
|
||||
}
|
||||
|
||||
if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_On)
|
||||
{
|
||||
if (p != startPoint)
|
||||
destShape.lineTo (x, y);
|
||||
}
|
||||
else if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Conic)
|
||||
{
|
||||
const int nextIndex = (p == endPoint) ? startPoint : p + 1;
|
||||
float x2 = scaleX * points [nextIndex].x;
|
||||
float y2 = scaleY * points [nextIndex].y;
|
||||
|
||||
if (FT_CURVE_TAG (tags [nextIndex]) == FT_Curve_Tag_Conic)
|
||||
{
|
||||
x2 = (x + x2) * 0.5f;
|
||||
y2 = (y + y2) * 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
++p;
|
||||
}
|
||||
|
||||
destShape.quadraticTo (x, y, x2, y2);
|
||||
}
|
||||
else if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Cubic)
|
||||
{
|
||||
const int next1 = p + 1;
|
||||
const int next2 = (p == (endPoint - 1)) ? startPoint : (p + 2);
|
||||
|
||||
if (p >= endPoint
|
||||
|| FT_CURVE_TAG (tags[next1]) != FT_Curve_Tag_Cubic
|
||||
|| FT_CURVE_TAG (tags[next2]) != FT_Curve_Tag_On)
|
||||
return false;
|
||||
|
||||
const float x2 = scaleX * points [next1].x;
|
||||
const float y2 = scaleY * points [next1].y;
|
||||
const float x3 = scaleX * points [next2].x;
|
||||
const float y3 = scaleY * points [next2].y;
|
||||
|
||||
destShape.cubicTo (x, y, x2, y2, x3, y3);
|
||||
p += 2;
|
||||
}
|
||||
}
|
||||
|
||||
destShape.closeSubPath();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void addKerning (FT_Face face, const uint32 character, const uint32 glyphIndex)
|
||||
{
|
||||
const float height = (float) (face->ascender - face->descender);
|
||||
|
||||
uint32 rightGlyphIndex;
|
||||
FT_ULong rightCharCode = FT_Get_First_Char (face, &rightGlyphIndex);
|
||||
|
||||
while (rightGlyphIndex != 0)
|
||||
{
|
||||
FT_Vector kerning;
|
||||
|
||||
if (FT_Get_Kerning (face, glyphIndex, rightGlyphIndex, ft_kerning_unscaled, &kerning) == 0
|
||||
&& kerning.x != 0)
|
||||
addKerningPair ((juce_wchar) character, (juce_wchar) rightCharCode, kerning.x / height);
|
||||
|
||||
rightCharCode = FT_Get_Next_Char (face, rightCharCode, &rightGlyphIndex);
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (FreeTypeTypeface)
|
||||
};
|
||||
|
||||
} // namespace juce
|
199
modules/juce_graphics/native/juce_linux_Fonts.cpp
Normal file
199
modules/juce_graphics/native/juce_linux_Fonts.cpp
Normal file
@ -0,0 +1,199 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
static XmlElement* findFontsConfFile()
|
||||
{
|
||||
static const char* pathsToSearch[] = { "/etc/fonts/fonts.conf",
|
||||
"/usr/share/fonts/fonts.conf" };
|
||||
|
||||
for (auto* path : pathsToSearch)
|
||||
if (auto* xml = XmlDocument::parse (File (path)))
|
||||
return xml;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
StringArray FTTypefaceList::getDefaultFontDirectories()
|
||||
{
|
||||
StringArray fontDirs;
|
||||
|
||||
fontDirs.addTokens (String (CharPointer_UTF8 (getenv ("JUCE_FONT_PATH"))), ";,", "");
|
||||
fontDirs.removeEmptyStrings (true);
|
||||
|
||||
if (fontDirs.isEmpty())
|
||||
{
|
||||
std::unique_ptr<XmlElement> fontsInfo (findFontsConfFile());
|
||||
|
||||
if (fontsInfo != nullptr)
|
||||
{
|
||||
forEachXmlChildElementWithTagName (*fontsInfo, e, "dir")
|
||||
{
|
||||
auto fontPath = e->getAllSubText().trim();
|
||||
|
||||
if (fontPath.isNotEmpty())
|
||||
{
|
||||
if (e->getStringAttribute ("prefix") == "xdg")
|
||||
{
|
||||
auto xdgDataHome = SystemStats::getEnvironmentVariable ("XDG_DATA_HOME", {});
|
||||
|
||||
if (xdgDataHome.trimStart().isEmpty())
|
||||
xdgDataHome = "~/.local/share";
|
||||
|
||||
fontPath = File (xdgDataHome).getChildFile (fontPath).getFullPathName();
|
||||
}
|
||||
|
||||
fontDirs.add (fontPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fontDirs.isEmpty())
|
||||
fontDirs.add ("/usr/X11R6/lib/X11/fonts");
|
||||
|
||||
fontDirs.removeDuplicates (false);
|
||||
return fontDirs;
|
||||
}
|
||||
|
||||
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
|
||||
{
|
||||
return new FreeTypeTypeface (font);
|
||||
}
|
||||
|
||||
Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize)
|
||||
{
|
||||
return new FreeTypeTypeface (data, dataSize);
|
||||
}
|
||||
|
||||
void Typeface::scanFolderForFonts (const File& folder)
|
||||
{
|
||||
FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName()));
|
||||
}
|
||||
|
||||
StringArray Font::findAllTypefaceNames()
|
||||
{
|
||||
return FTTypefaceList::getInstance()->findAllFamilyNames();
|
||||
}
|
||||
|
||||
StringArray Font::findAllTypefaceStyles (const String& family)
|
||||
{
|
||||
return FTTypefaceList::getInstance()->findAllTypefaceStyles (family);
|
||||
}
|
||||
|
||||
bool TextLayout::createNativeLayout (const AttributedString&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct DefaultFontNames
|
||||
{
|
||||
DefaultFontNames()
|
||||
: defaultSans (getDefaultSansSerifFontName()),
|
||||
defaultSerif (getDefaultSerifFontName()),
|
||||
defaultFixed (getDefaultMonospacedFontName())
|
||||
{
|
||||
}
|
||||
|
||||
String getRealFontName (const String& faceName) const
|
||||
{
|
||||
if (faceName == Font::getDefaultSansSerifFontName()) return defaultSans;
|
||||
if (faceName == Font::getDefaultSerifFontName()) return defaultSerif;
|
||||
if (faceName == Font::getDefaultMonospacedFontName()) return defaultFixed;
|
||||
|
||||
return faceName;
|
||||
}
|
||||
|
||||
String defaultSans, defaultSerif, defaultFixed;
|
||||
|
||||
private:
|
||||
static String pickBestFont (const StringArray& names, const char* const* choicesArray)
|
||||
{
|
||||
const StringArray choices (choicesArray);
|
||||
|
||||
for (auto& choice : choices)
|
||||
if (names.contains (choice, true))
|
||||
return choice;
|
||||
|
||||
for (auto& choice : choices)
|
||||
for (auto& name : names)
|
||||
if (name.startsWithIgnoreCase (choice))
|
||||
return name;
|
||||
|
||||
for (auto& choice : choices)
|
||||
for (auto& name : names)
|
||||
if (name.containsIgnoreCase (choice))
|
||||
return name;
|
||||
|
||||
return names[0];
|
||||
}
|
||||
|
||||
static String getDefaultSansSerifFontName()
|
||||
{
|
||||
StringArray allFonts;
|
||||
FTTypefaceList::getInstance()->getSansSerifNames (allFonts);
|
||||
|
||||
static const char* targets[] = { "Verdana", "Bitstream Vera Sans", "Luxi Sans",
|
||||
"Liberation Sans", "DejaVu Sans", "Sans", nullptr };
|
||||
return pickBestFont (allFonts, targets);
|
||||
}
|
||||
|
||||
static String getDefaultSerifFontName()
|
||||
{
|
||||
StringArray allFonts;
|
||||
FTTypefaceList::getInstance()->getSerifNames (allFonts);
|
||||
|
||||
static const char* targets[] = { "Bitstream Vera Serif", "Times", "Nimbus Roman",
|
||||
"Liberation Serif", "DejaVu Serif", "Serif", nullptr };
|
||||
return pickBestFont (allFonts, targets);
|
||||
}
|
||||
|
||||
static String getDefaultMonospacedFontName()
|
||||
{
|
||||
StringArray allFonts;
|
||||
FTTypefaceList::getInstance()->getMonospacedNames (allFonts);
|
||||
|
||||
static const char* targets[] = { "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Sans Mono",
|
||||
"Liberation Mono", "Courier", "DejaVu Mono", "Mono", nullptr };
|
||||
return pickBestFont (allFonts, targets);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (DefaultFontNames)
|
||||
};
|
||||
|
||||
Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
|
||||
{
|
||||
static DefaultFontNames defaultNames;
|
||||
|
||||
Font f (font);
|
||||
f.setTypefaceName (defaultNames.getRealFontName (font.getTypefaceName()));
|
||||
return Typeface::createSystemTypefaceFor (f);
|
||||
}
|
||||
|
||||
} // namespace juce
|
30
modules/juce_graphics/native/juce_linux_IconHelpers.cpp
Normal file
30
modules/juce_graphics/native/juce_linux_IconHelpers.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
Image JUCE_API getIconFromApplication (const String&, int) { return {}; }
|
||||
}
|
115
modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h
Normal file
115
modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class CoreGraphicsContext : public LowLevelGraphicsContext
|
||||
{
|
||||
public:
|
||||
CoreGraphicsContext (CGContextRef context, float flipHeight, float targetScale);
|
||||
~CoreGraphicsContext();
|
||||
|
||||
//==============================================================================
|
||||
bool isVectorDevice() const override { return false; }
|
||||
|
||||
void setOrigin (Point<int>) override;
|
||||
void addTransform (const AffineTransform&) override;
|
||||
float getPhysicalPixelScaleFactor() override;
|
||||
bool clipToRectangle (const Rectangle<int>&) override;
|
||||
bool clipToRectangleList (const RectangleList<int>&) override;
|
||||
void excludeClipRectangle (const Rectangle<int>&) override;
|
||||
void clipToPath (const Path&, const AffineTransform&) override;
|
||||
void clipToImageAlpha (const Image&, const AffineTransform&) override;
|
||||
bool clipRegionIntersects (const Rectangle<int>&) override;
|
||||
Rectangle<int> getClipBounds() const override;
|
||||
bool isClipEmpty() const override;
|
||||
|
||||
//==============================================================================
|
||||
void saveState() override;
|
||||
void restoreState() override;
|
||||
void beginTransparencyLayer (float opacity) override;
|
||||
void endTransparencyLayer() override;
|
||||
|
||||
//==============================================================================
|
||||
void setFill (const FillType&) override;
|
||||
void setOpacity (float) override;
|
||||
void setInterpolationQuality (Graphics::ResamplingQuality) override;
|
||||
|
||||
//==============================================================================
|
||||
void fillRect (const Rectangle<int>&, bool replaceExistingContents) override;
|
||||
void fillRect (const Rectangle<float>&) override;
|
||||
void fillRectList (const RectangleList<float>&) override;
|
||||
void fillPath (const Path&, const AffineTransform&) override;
|
||||
void drawImage (const Image& sourceImage, const AffineTransform&) override;
|
||||
|
||||
//==============================================================================
|
||||
void drawLine (const Line<float>&) override;
|
||||
void setFont (const Font&) override;
|
||||
const Font& getFont() override;
|
||||
void drawGlyph (int glyphNumber, const AffineTransform&) override;
|
||||
bool drawTextLayout (const AttributedString&, const Rectangle<float>&) override;
|
||||
|
||||
private:
|
||||
CGContextRef context;
|
||||
const CGFloat flipHeight;
|
||||
float targetScale;
|
||||
CGColorSpaceRef rgbColourSpace, greyColourSpace;
|
||||
mutable Rectangle<int> lastClipRect;
|
||||
mutable bool lastClipRectIsValid;
|
||||
|
||||
struct SavedState
|
||||
{
|
||||
SavedState();
|
||||
SavedState (const SavedState&);
|
||||
~SavedState();
|
||||
|
||||
void setFill (const FillType&);
|
||||
|
||||
FillType fillType;
|
||||
Font font;
|
||||
CGFontRef fontRef;
|
||||
CGAffineTransform fontTransform;
|
||||
CGGradientRef gradient;
|
||||
};
|
||||
|
||||
std::unique_ptr<SavedState> state;
|
||||
OwnedArray<SavedState> stateStack;
|
||||
|
||||
void drawGradient();
|
||||
void createPath (const Path&) const;
|
||||
void createPath (const Path&, const AffineTransform&) const;
|
||||
void flip() const;
|
||||
void applyTransform (const AffineTransform&) const;
|
||||
void drawImage (const Image&, const AffineTransform&, bool fillEntireClipAsTiles);
|
||||
bool clipToRectangleListWithoutTest (const RectangleList<int>&);
|
||||
void fillCGRect (const CGRect&, bool replaceExistingContents);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsContext)
|
||||
};
|
||||
|
||||
} // namespace juce
|
946
modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm
Normal file
946
modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm
Normal file
@ -0,0 +1,946 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class CoreGraphicsImage : public ImagePixelData
|
||||
{
|
||||
public:
|
||||
CoreGraphicsImage (const Image::PixelFormat format, const int w, const int h, const bool clearImage)
|
||||
: ImagePixelData (format, w, h), cachedImageRef (0)
|
||||
{
|
||||
pixelStride = format == Image::RGB ? 3 : ((format == Image::ARGB) ? 4 : 1);
|
||||
lineStride = (pixelStride * jmax (1, width) + 3) & ~3;
|
||||
|
||||
imageData.allocate ((size_t) lineStride * (size_t) jmax (1, height), clearImage);
|
||||
|
||||
CGColorSpaceRef colourSpace = (format == Image::SingleChannel) ? CGColorSpaceCreateDeviceGray()
|
||||
: CGColorSpaceCreateDeviceRGB();
|
||||
|
||||
context = CGBitmapContextCreate (imageData, (size_t) width, (size_t) height, 8, (size_t) lineStride,
|
||||
colourSpace, getCGImageFlags (format));
|
||||
|
||||
CGColorSpaceRelease (colourSpace);
|
||||
}
|
||||
|
||||
~CoreGraphicsImage()
|
||||
{
|
||||
freeCachedImageRef();
|
||||
CGContextRelease (context);
|
||||
}
|
||||
|
||||
LowLevelGraphicsContext* createLowLevelContext() override
|
||||
{
|
||||
freeCachedImageRef();
|
||||
sendDataChangeMessage();
|
||||
return new CoreGraphicsContext (context, height, 1.0f);
|
||||
}
|
||||
|
||||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override
|
||||
{
|
||||
bitmap.data = imageData + x * pixelStride + y * lineStride;
|
||||
bitmap.pixelFormat = pixelFormat;
|
||||
bitmap.lineStride = lineStride;
|
||||
bitmap.pixelStride = pixelStride;
|
||||
|
||||
if (mode != Image::BitmapData::readOnly)
|
||||
{
|
||||
freeCachedImageRef();
|
||||
sendDataChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
ImagePixelData::Ptr clone() override
|
||||
{
|
||||
CoreGraphicsImage* im = new CoreGraphicsImage (pixelFormat, width, height, false);
|
||||
memcpy (im->imageData, imageData, (size_t) (lineStride * height));
|
||||
return im;
|
||||
}
|
||||
|
||||
ImageType* createType() const override { return new NativeImageType(); }
|
||||
|
||||
//==============================================================================
|
||||
static CGImageRef getCachedImageRef (const Image& juceImage, CGColorSpaceRef colourSpace)
|
||||
{
|
||||
CoreGraphicsImage* const cgim = dynamic_cast<CoreGraphicsImage*> (juceImage.getPixelData());
|
||||
|
||||
if (cgim != nullptr && cgim->cachedImageRef != 0)
|
||||
{
|
||||
CGImageRetain (cgim->cachedImageRef);
|
||||
return cgim->cachedImageRef;
|
||||
}
|
||||
|
||||
CGImageRef ref = createImage (juceImage, colourSpace, false);
|
||||
|
||||
if (cgim != nullptr)
|
||||
{
|
||||
CGImageRetain (ref);
|
||||
cgim->cachedImageRef = ref;
|
||||
}
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
static CGImageRef createImage (const Image& juceImage, CGColorSpaceRef colourSpace, const bool mustOutliveSource)
|
||||
{
|
||||
const Image::BitmapData srcData (juceImage, Image::BitmapData::readOnly);
|
||||
CGDataProviderRef provider;
|
||||
|
||||
if (mustOutliveSource)
|
||||
{
|
||||
CFDataRef data = CFDataCreate (0, (const UInt8*) srcData.data, (CFIndex) ((size_t) srcData.lineStride * (size_t) srcData.height));
|
||||
provider = CGDataProviderCreateWithCFData (data);
|
||||
CFRelease (data);
|
||||
}
|
||||
else
|
||||
{
|
||||
provider = CGDataProviderCreateWithData (0, srcData.data, (size_t) srcData.lineStride * (size_t) srcData.height, 0);
|
||||
}
|
||||
|
||||
CGImageRef imageRef = CGImageCreate ((size_t) srcData.width,
|
||||
(size_t) srcData.height,
|
||||
8, (size_t) srcData.pixelStride * 8,
|
||||
(size_t) srcData.lineStride,
|
||||
colourSpace, getCGImageFlags (juceImage.getFormat()), provider,
|
||||
0, true, kCGRenderingIntentDefault);
|
||||
|
||||
CGDataProviderRelease (provider);
|
||||
return imageRef;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
CGContextRef context;
|
||||
CGImageRef cachedImageRef;
|
||||
HeapBlock<uint8> imageData;
|
||||
int pixelStride, lineStride;
|
||||
|
||||
private:
|
||||
void freeCachedImageRef()
|
||||
{
|
||||
if (cachedImageRef != 0)
|
||||
{
|
||||
CGImageRelease (cachedImageRef);
|
||||
cachedImageRef = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
|
||||
{
|
||||
#if JUCE_BIG_ENDIAN
|
||||
return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big) : kCGBitmapByteOrderDefault;
|
||||
#else
|
||||
return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) : kCGBitmapByteOrderDefault;
|
||||
#endif
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsImage)
|
||||
};
|
||||
|
||||
ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const
|
||||
{
|
||||
return new CoreGraphicsImage (format == Image::RGB ? Image::ARGB : format, width, height, clearImage);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
CoreGraphicsContext::CoreGraphicsContext (CGContextRef c, const float h, const float scale)
|
||||
: context (c),
|
||||
flipHeight (h),
|
||||
targetScale (scale),
|
||||
lastClipRectIsValid (false),
|
||||
state (new SavedState())
|
||||
{
|
||||
CGContextRetain (context);
|
||||
CGContextSaveGState (context);
|
||||
CGContextSetShouldSmoothFonts (context, true);
|
||||
CGContextSetAllowsFontSmoothing (context, true);
|
||||
CGContextSetShouldAntialias (context, true);
|
||||
CGContextSetBlendMode (context, kCGBlendModeNormal);
|
||||
rgbColourSpace = CGColorSpaceCreateDeviceRGB();
|
||||
greyColourSpace = CGColorSpaceCreateDeviceGray();
|
||||
setFont (Font());
|
||||
}
|
||||
|
||||
CoreGraphicsContext::~CoreGraphicsContext()
|
||||
{
|
||||
CGContextRestoreGState (context);
|
||||
CGContextRelease (context);
|
||||
CGColorSpaceRelease (rgbColourSpace);
|
||||
CGColorSpaceRelease (greyColourSpace);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CoreGraphicsContext::setOrigin (Point<int> o)
|
||||
{
|
||||
CGContextTranslateCTM (context, o.x, -o.y);
|
||||
|
||||
if (lastClipRectIsValid)
|
||||
lastClipRect.translate (-o.x, -o.y);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::addTransform (const AffineTransform& transform)
|
||||
{
|
||||
applyTransform (AffineTransform::verticalFlip ((float) flipHeight)
|
||||
.followedBy (transform)
|
||||
.translated (0, (float) -flipHeight)
|
||||
.scaled (1.0f, -1.0f));
|
||||
lastClipRectIsValid = false;
|
||||
|
||||
jassert (getPhysicalPixelScaleFactor() > 0.0f);
|
||||
jassert (getPhysicalPixelScaleFactor() > 0.0f);
|
||||
}
|
||||
|
||||
float CoreGraphicsContext::getPhysicalPixelScaleFactor()
|
||||
{
|
||||
const CGAffineTransform t = CGContextGetCTM (context);
|
||||
|
||||
return targetScale * (float) (juce_hypot (t.a, t.c) + juce_hypot (t.b, t.d)) / 2.0f;
|
||||
}
|
||||
|
||||
bool CoreGraphicsContext::clipToRectangle (const Rectangle<int>& r)
|
||||
{
|
||||
CGContextClipToRect (context, CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()));
|
||||
|
||||
if (lastClipRectIsValid)
|
||||
{
|
||||
// This is actually incorrect, because the actual clip region may be complex, and
|
||||
// clipping its bounds to a rect may not be right... But, removing this shortcut
|
||||
// doesn't actually fix anything because CoreGraphics also ignores complex regions
|
||||
// when calculating the resultant clip bounds, and makes the same mistake!
|
||||
lastClipRect = lastClipRect.getIntersection (r);
|
||||
return ! lastClipRect.isEmpty();
|
||||
}
|
||||
|
||||
return ! isClipEmpty();
|
||||
}
|
||||
|
||||
bool CoreGraphicsContext::clipToRectangleListWithoutTest (const RectangleList<int>& clipRegion)
|
||||
{
|
||||
if (clipRegion.isEmpty())
|
||||
{
|
||||
CGContextClipToRect (context, CGRectZero);
|
||||
lastClipRectIsValid = true;
|
||||
lastClipRect = Rectangle<int>();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto numRects = (size_t) clipRegion.getNumRectangles();
|
||||
HeapBlock<CGRect> rects (numRects);
|
||||
|
||||
int i = 0;
|
||||
for (auto& r : clipRegion)
|
||||
rects[i++] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight());
|
||||
|
||||
CGContextClipToRects (context, rects, numRects);
|
||||
lastClipRectIsValid = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CoreGraphicsContext::clipToRectangleList (const RectangleList<int>& clipRegion)
|
||||
{
|
||||
return clipToRectangleListWithoutTest (clipRegion) && ! isClipEmpty();
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::excludeClipRectangle (const Rectangle<int>& r)
|
||||
{
|
||||
RectangleList<int> remaining (getClipBounds());
|
||||
remaining.subtract (r);
|
||||
clipToRectangleListWithoutTest (remaining);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform)
|
||||
{
|
||||
createPath (path, transform);
|
||||
|
||||
if (path.isUsingNonZeroWinding())
|
||||
CGContextClip (context);
|
||||
else
|
||||
CGContextEOClip (context);
|
||||
|
||||
lastClipRectIsValid = false;
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform)
|
||||
{
|
||||
if (! transform.isSingularity())
|
||||
{
|
||||
Image singleChannelImage (sourceImage);
|
||||
|
||||
if (sourceImage.getFormat() != Image::SingleChannel)
|
||||
singleChannelImage = sourceImage.convertedToFormat (Image::SingleChannel);
|
||||
|
||||
CGImageRef image = CoreGraphicsImage::createImage (singleChannelImage, greyColourSpace, true);
|
||||
|
||||
flip();
|
||||
AffineTransform t (AffineTransform::verticalFlip (sourceImage.getHeight()).followedBy (transform));
|
||||
applyTransform (t);
|
||||
|
||||
CGRect r = convertToCGRect (sourceImage.getBounds());
|
||||
CGContextClipToMask (context, r, image);
|
||||
|
||||
applyTransform (t.inverted());
|
||||
flip();
|
||||
|
||||
CGImageRelease (image);
|
||||
lastClipRectIsValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CoreGraphicsContext::clipRegionIntersects (const Rectangle<int>& r)
|
||||
{
|
||||
return getClipBounds().intersects (r);
|
||||
}
|
||||
|
||||
Rectangle<int> CoreGraphicsContext::getClipBounds() const
|
||||
{
|
||||
if (! lastClipRectIsValid)
|
||||
{
|
||||
CGRect bounds = CGRectIntegral (CGContextGetClipBoundingBox (context));
|
||||
|
||||
lastClipRectIsValid = true;
|
||||
lastClipRect.setBounds (roundToInt (bounds.origin.x),
|
||||
roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)),
|
||||
roundToInt (bounds.size.width),
|
||||
roundToInt (bounds.size.height));
|
||||
}
|
||||
|
||||
return lastClipRect;
|
||||
}
|
||||
|
||||
bool CoreGraphicsContext::isClipEmpty() const
|
||||
{
|
||||
return getClipBounds().isEmpty();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CoreGraphicsContext::saveState()
|
||||
{
|
||||
CGContextSaveGState (context);
|
||||
stateStack.add (new SavedState (*state));
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::restoreState()
|
||||
{
|
||||
CGContextRestoreGState (context);
|
||||
|
||||
if (auto* top = stateStack.getLast())
|
||||
{
|
||||
state.reset (top);
|
||||
stateStack.removeLast (1, false);
|
||||
lastClipRectIsValid = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse; // trying to pop with an empty stack!
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::beginTransparencyLayer (float opacity)
|
||||
{
|
||||
saveState();
|
||||
CGContextSetAlpha (context, opacity);
|
||||
CGContextBeginTransparencyLayer (context, 0);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::endTransparencyLayer()
|
||||
{
|
||||
CGContextEndTransparencyLayer (context);
|
||||
restoreState();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CoreGraphicsContext::setFill (const FillType& fillType)
|
||||
{
|
||||
state->setFill (fillType);
|
||||
|
||||
if (fillType.isColour())
|
||||
{
|
||||
CGContextSetRGBFillColor (context, fillType.colour.getFloatRed(), fillType.colour.getFloatGreen(),
|
||||
fillType.colour.getFloatBlue(), fillType.colour.getFloatAlpha());
|
||||
CGContextSetAlpha (context, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::setOpacity (float newOpacity)
|
||||
{
|
||||
state->fillType.setOpacity (newOpacity);
|
||||
setFill (state->fillType);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::setInterpolationQuality (Graphics::ResamplingQuality quality)
|
||||
{
|
||||
switch (quality)
|
||||
{
|
||||
case Graphics::lowResamplingQuality: CGContextSetInterpolationQuality (context, kCGInterpolationNone); return;
|
||||
case Graphics::mediumResamplingQuality: CGContextSetInterpolationQuality (context, kCGInterpolationMedium); return;
|
||||
case Graphics::highResamplingQuality: CGContextSetInterpolationQuality (context, kCGInterpolationHigh); return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CoreGraphicsContext::fillRect (const Rectangle<int>& r, const bool replaceExistingContents)
|
||||
{
|
||||
fillCGRect (CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()), replaceExistingContents);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::fillRect (const Rectangle<float>& r)
|
||||
{
|
||||
fillCGRect (CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()), false);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::fillCGRect (const CGRect& cgRect, const bool replaceExistingContents)
|
||||
{
|
||||
if (replaceExistingContents)
|
||||
{
|
||||
CGContextSetBlendMode (context, kCGBlendModeCopy);
|
||||
fillCGRect (cgRect, false);
|
||||
CGContextSetBlendMode (context, kCGBlendModeNormal);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state->fillType.isColour())
|
||||
{
|
||||
CGContextFillRect (context, cgRect);
|
||||
}
|
||||
else if (state->fillType.isGradient())
|
||||
{
|
||||
CGContextSaveGState (context);
|
||||
CGContextClipToRect (context, cgRect);
|
||||
drawGradient();
|
||||
CGContextRestoreGState (context);
|
||||
}
|
||||
else
|
||||
{
|
||||
CGContextSaveGState (context);
|
||||
CGContextClipToRect (context, cgRect);
|
||||
drawImage (state->fillType.image, state->fillType.transform, true);
|
||||
CGContextRestoreGState (context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::fillPath (const Path& path, const AffineTransform& transform)
|
||||
{
|
||||
CGContextSaveGState (context);
|
||||
|
||||
if (state->fillType.isColour())
|
||||
{
|
||||
flip();
|
||||
applyTransform (transform);
|
||||
createPath (path);
|
||||
|
||||
if (path.isUsingNonZeroWinding())
|
||||
CGContextFillPath (context);
|
||||
else
|
||||
CGContextEOFillPath (context);
|
||||
}
|
||||
else
|
||||
{
|
||||
createPath (path, transform);
|
||||
|
||||
if (path.isUsingNonZeroWinding())
|
||||
CGContextClip (context);
|
||||
else
|
||||
CGContextEOClip (context);
|
||||
|
||||
if (state->fillType.isGradient())
|
||||
drawGradient();
|
||||
else
|
||||
drawImage (state->fillType.image, state->fillType.transform, true);
|
||||
}
|
||||
|
||||
CGContextRestoreGState (context);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform)
|
||||
{
|
||||
drawImage (sourceImage, transform, false);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform, const bool fillEntireClipAsTiles)
|
||||
{
|
||||
const int iw = sourceImage.getWidth();
|
||||
const int ih = sourceImage.getHeight();
|
||||
CGImageRef image = CoreGraphicsImage::getCachedImageRef (sourceImage, sourceImage.getFormat() == Image::PixelFormat::SingleChannel ? greyColourSpace
|
||||
: rgbColourSpace);
|
||||
|
||||
CGContextSaveGState (context);
|
||||
CGContextSetAlpha (context, state->fillType.getOpacity());
|
||||
|
||||
flip();
|
||||
applyTransform (AffineTransform::verticalFlip (ih).followedBy (transform));
|
||||
CGRect imageRect = CGRectMake (0, 0, iw, ih);
|
||||
|
||||
if (fillEntireClipAsTiles)
|
||||
{
|
||||
#if JUCE_IOS
|
||||
CGContextDrawTiledImage (context, imageRect, image);
|
||||
#else
|
||||
// There's a bug in CGContextDrawTiledImage that makes it incredibly slow
|
||||
// if it's doing a transformation - it's quicker to just draw lots of images manually
|
||||
if (&CGContextDrawTiledImage != 0 && transform.isOnlyTranslation())
|
||||
{
|
||||
CGContextDrawTiledImage (context, imageRect, image);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to manually doing a tiled fill
|
||||
CGRect clip = CGRectIntegral (CGContextGetClipBoundingBox (context));
|
||||
|
||||
int x = 0, y = 0;
|
||||
while (x > clip.origin.x) x -= iw;
|
||||
while (y > clip.origin.y) y -= ih;
|
||||
|
||||
const int right = (int) (clip.origin.x + clip.size.width);
|
||||
const int bottom = (int) (clip.origin.y + clip.size.height);
|
||||
|
||||
while (y < bottom)
|
||||
{
|
||||
for (int x2 = x; x2 < right; x2 += iw)
|
||||
CGContextDrawImage (context, CGRectMake (x2, y, iw, ih), image);
|
||||
|
||||
y += ih;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
CGContextDrawImage (context, imageRect, image);
|
||||
}
|
||||
|
||||
CGImageRelease (image); // (This causes a memory bug in iOS sim 3.0 - try upgrading to a later version if you hit this)
|
||||
CGContextRestoreGState (context);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CoreGraphicsContext::drawLine (const Line<float>& line)
|
||||
{
|
||||
if (state->fillType.isColour())
|
||||
{
|
||||
CGContextSetLineCap (context, kCGLineCapSquare);
|
||||
CGContextSetLineWidth (context, 1.0f);
|
||||
CGContextSetRGBStrokeColor (context,
|
||||
state->fillType.colour.getFloatRed(), state->fillType.colour.getFloatGreen(),
|
||||
state->fillType.colour.getFloatBlue(), state->fillType.colour.getFloatAlpha());
|
||||
|
||||
CGPoint cgLine[] = { { (CGFloat) line.getStartX(), flipHeight - (CGFloat) line.getStartY() },
|
||||
{ (CGFloat) line.getEndX(), flipHeight - (CGFloat) line.getEndY() } };
|
||||
|
||||
CGContextStrokeLineSegments (context, cgLine, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Path p;
|
||||
p.addLineSegment (line, 1.0f);
|
||||
fillPath (p, AffineTransform());
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::fillRectList (const RectangleList<float>& list)
|
||||
{
|
||||
HeapBlock<CGRect> rects (list.getNumRectangles());
|
||||
|
||||
size_t num = 0;
|
||||
|
||||
for (auto& r : list)
|
||||
rects[num++] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight());
|
||||
|
||||
if (state->fillType.isColour())
|
||||
{
|
||||
CGContextFillRects (context, rects, num);
|
||||
}
|
||||
else if (state->fillType.isGradient())
|
||||
{
|
||||
CGContextSaveGState (context);
|
||||
CGContextClipToRects (context, rects, num);
|
||||
drawGradient();
|
||||
CGContextRestoreGState (context);
|
||||
}
|
||||
else
|
||||
{
|
||||
CGContextSaveGState (context);
|
||||
CGContextClipToRects (context, rects, num);
|
||||
drawImage (state->fillType.image, state->fillType.transform, true);
|
||||
CGContextRestoreGState (context);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::setFont (const Font& newFont)
|
||||
{
|
||||
if (state->font != newFont)
|
||||
{
|
||||
state->fontRef = 0;
|
||||
state->font = newFont;
|
||||
|
||||
if (OSXTypeface* osxTypeface = dynamic_cast<OSXTypeface*> (state->font.getTypeface()))
|
||||
{
|
||||
state->fontRef = osxTypeface->fontRef;
|
||||
CGContextSetFont (context, state->fontRef);
|
||||
CGContextSetFontSize (context, state->font.getHeight() * osxTypeface->fontHeightToPointsFactor);
|
||||
|
||||
state->fontTransform = osxTypeface->renderingTransform;
|
||||
state->fontTransform.a *= state->font.getHorizontalScale();
|
||||
CGContextSetTextMatrix (context, state->fontTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Font& CoreGraphicsContext::getFont()
|
||||
{
|
||||
return state->font;
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& transform)
|
||||
{
|
||||
if (state->fontRef != 0 && state->fillType.isColour())
|
||||
{
|
||||
#if JUCE_CLANG
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
|
||||
if (transform.isOnlyTranslation())
|
||||
{
|
||||
CGContextSetTextMatrix (context, state->fontTransform); // have to set this each time, as it's not saved as part of the state
|
||||
|
||||
CGGlyph g = (CGGlyph) glyphNumber;
|
||||
CGContextShowGlyphsAtPoint (context, transform.getTranslationX(),
|
||||
flipHeight - roundToInt (transform.getTranslationY()), &g, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
CGContextSaveGState (context);
|
||||
flip();
|
||||
applyTransform (transform);
|
||||
|
||||
CGAffineTransform t = state->fontTransform;
|
||||
t.d = -t.d;
|
||||
CGContextSetTextMatrix (context, t);
|
||||
|
||||
CGGlyph g = (CGGlyph) glyphNumber;
|
||||
CGContextShowGlyphsAtPoint (context, 0, 0, &g, 1);
|
||||
|
||||
CGContextRestoreGState (context);
|
||||
}
|
||||
|
||||
#if JUCE_CLANG
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
Path p;
|
||||
Font& f = state->font;
|
||||
f.getTypeface()->getOutlineForGlyph (glyphNumber, p);
|
||||
|
||||
fillPath (p, AffineTransform::scale (f.getHeight() * f.getHorizontalScale(), f.getHeight())
|
||||
.followedBy (transform));
|
||||
}
|
||||
}
|
||||
|
||||
bool CoreGraphicsContext::drawTextLayout (const AttributedString& text, const Rectangle<float>& area)
|
||||
{
|
||||
CoreTextTypeLayout::drawToCGContext (text, area, context, (float) flipHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
CoreGraphicsContext::SavedState::SavedState()
|
||||
: font (1.0f), fontRef (0), fontTransform (CGAffineTransformIdentity), gradient (0)
|
||||
{
|
||||
}
|
||||
|
||||
CoreGraphicsContext::SavedState::SavedState (const SavedState& other)
|
||||
: fillType (other.fillType), font (other.font), fontRef (other.fontRef),
|
||||
fontTransform (other.fontTransform), gradient (other.gradient)
|
||||
{
|
||||
if (gradient != 0)
|
||||
CGGradientRetain (gradient);
|
||||
}
|
||||
|
||||
CoreGraphicsContext::SavedState::~SavedState()
|
||||
{
|
||||
if (gradient != 0)
|
||||
CGGradientRelease (gradient);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::SavedState::setFill (const FillType& newFill)
|
||||
{
|
||||
fillType = newFill;
|
||||
|
||||
if (gradient != 0)
|
||||
{
|
||||
CGGradientRelease (gradient);
|
||||
gradient = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static CGGradientRef createGradient (const ColourGradient& g, CGColorSpaceRef colourSpace)
|
||||
{
|
||||
const int numColours = g.getNumColours();
|
||||
CGFloat* const data = (CGFloat*) alloca ((size_t) numColours * 5 * sizeof (CGFloat));
|
||||
CGFloat* const locations = data;
|
||||
CGFloat* const components = data + numColours;
|
||||
CGFloat* comps = components;
|
||||
|
||||
for (int i = 0; i < numColours; ++i)
|
||||
{
|
||||
const Colour colour (g.getColour (i));
|
||||
*comps++ = (CGFloat) colour.getFloatRed();
|
||||
*comps++ = (CGFloat) colour.getFloatGreen();
|
||||
*comps++ = (CGFloat) colour.getFloatBlue();
|
||||
*comps++ = (CGFloat) colour.getFloatAlpha();
|
||||
locations[i] = (CGFloat) g.getColourPosition (i);
|
||||
|
||||
// There's a bug (?) in the way the CG renderer works where it seems
|
||||
// to go wrong if you have two colour stops both at position 0..
|
||||
jassert (i == 0 || locations[i] != 0);
|
||||
}
|
||||
|
||||
return CGGradientCreateWithColorComponents (colourSpace, components, locations, (size_t) numColours);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::drawGradient()
|
||||
{
|
||||
flip();
|
||||
applyTransform (state->fillType.transform);
|
||||
CGContextSetAlpha (context, state->fillType.getOpacity());
|
||||
|
||||
const ColourGradient& g = *state->fillType.gradient;
|
||||
|
||||
CGPoint p1 (convertToCGPoint (g.point1));
|
||||
CGPoint p2 (convertToCGPoint (g.point2));
|
||||
|
||||
state->fillType.transform.transformPoints (p1.x, p1.y, p2.x, p2.y);
|
||||
|
||||
if (state->gradient == 0)
|
||||
state->gradient = createGradient (g, rgbColourSpace);
|
||||
|
||||
if (g.isRadial)
|
||||
CGContextDrawRadialGradient (context, state->gradient, p1, 0, p1, g.point1.getDistanceFrom (g.point2),
|
||||
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
||||
else
|
||||
CGContextDrawLinearGradient (context, state->gradient, p1, p2,
|
||||
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::createPath (const Path& path) const
|
||||
{
|
||||
CGContextBeginPath (context);
|
||||
|
||||
for (Path::Iterator i (path); i.next();)
|
||||
{
|
||||
switch (i.elementType)
|
||||
{
|
||||
case Path::Iterator::startNewSubPath: CGContextMoveToPoint (context, i.x1, i.y1); break;
|
||||
case Path::Iterator::lineTo: CGContextAddLineToPoint (context, i.x1, i.y1); break;
|
||||
case Path::Iterator::quadraticTo: CGContextAddQuadCurveToPoint (context, i.x1, i.y1, i.x2, i.y2); break;
|
||||
case Path::Iterator::cubicTo: CGContextAddCurveToPoint (context, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); break;
|
||||
case Path::Iterator::closePath: CGContextClosePath (context); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::createPath (const Path& path, const AffineTransform& transform) const
|
||||
{
|
||||
CGContextBeginPath (context);
|
||||
|
||||
for (Path::Iterator i (path); i.next();)
|
||||
{
|
||||
switch (i.elementType)
|
||||
{
|
||||
case Path::Iterator::startNewSubPath:
|
||||
transform.transformPoint (i.x1, i.y1);
|
||||
CGContextMoveToPoint (context, i.x1, flipHeight - i.y1);
|
||||
break;
|
||||
case Path::Iterator::lineTo:
|
||||
transform.transformPoint (i.x1, i.y1);
|
||||
CGContextAddLineToPoint (context, i.x1, flipHeight - i.y1);
|
||||
break;
|
||||
case Path::Iterator::quadraticTo:
|
||||
transform.transformPoints (i.x1, i.y1, i.x2, i.y2);
|
||||
CGContextAddQuadCurveToPoint (context, i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2);
|
||||
break;
|
||||
case Path::Iterator::cubicTo:
|
||||
transform.transformPoints (i.x1, i.y1, i.x2, i.y2, i.x3, i.y3);
|
||||
CGContextAddCurveToPoint (context, i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2, i.x3, flipHeight - i.y3);
|
||||
break;
|
||||
case Path::Iterator::closePath:
|
||||
CGContextClosePath (context); break;
|
||||
default:
|
||||
jassertfalse;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::flip() const
|
||||
{
|
||||
CGContextConcatCTM (context, CGAffineTransformMake (1, 0, 0, -1, 0, flipHeight));
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::applyTransform (const AffineTransform& transform) const
|
||||
{
|
||||
CGAffineTransform t;
|
||||
t.a = transform.mat00;
|
||||
t.b = transform.mat10;
|
||||
t.c = transform.mat01;
|
||||
t.d = transform.mat11;
|
||||
t.tx = transform.mat02;
|
||||
t.ty = transform.mat12;
|
||||
CGContextConcatCTM (context, t);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if USE_COREGRAPHICS_RENDERING && JUCE_USE_COREIMAGE_LOADER
|
||||
Image juce_loadWithCoreImage (InputStream& input)
|
||||
{
|
||||
MemoryBlock data;
|
||||
input.readIntoMemoryBlock (data, -1);
|
||||
|
||||
#if JUCE_IOS
|
||||
JUCE_AUTORELEASEPOOL
|
||||
#endif
|
||||
{
|
||||
#if JUCE_IOS
|
||||
if (UIImage* uiImage = [UIImage imageWithData: [NSData dataWithBytesNoCopy: data.getData()
|
||||
length: data.getSize()
|
||||
freeWhenDone: NO]])
|
||||
{
|
||||
CGImageRef loadedImage = uiImage.CGImage;
|
||||
|
||||
#else
|
||||
CGDataProviderRef provider = CGDataProviderCreateWithData (0, data.getData(), data.getSize(), 0);
|
||||
CGImageSourceRef imageSource = CGImageSourceCreateWithDataProvider (provider, 0);
|
||||
CGDataProviderRelease (provider);
|
||||
|
||||
if (imageSource != 0)
|
||||
{
|
||||
CGImageRef loadedImage = CGImageSourceCreateImageAtIndex (imageSource, 0, 0);
|
||||
CFRelease (imageSource);
|
||||
#endif
|
||||
|
||||
if (loadedImage != 0)
|
||||
{
|
||||
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo (loadedImage);
|
||||
const bool hasAlphaChan = (alphaInfo != kCGImageAlphaNone
|
||||
&& alphaInfo != kCGImageAlphaNoneSkipLast
|
||||
&& alphaInfo != kCGImageAlphaNoneSkipFirst);
|
||||
|
||||
Image image (NativeImageType().create (Image::ARGB, // (CoreImage doesn't work with 24-bit images)
|
||||
(int) CGImageGetWidth (loadedImage),
|
||||
(int) CGImageGetHeight (loadedImage),
|
||||
hasAlphaChan));
|
||||
|
||||
CoreGraphicsImage* const cgImage = dynamic_cast<CoreGraphicsImage*> (image.getPixelData());
|
||||
jassert (cgImage != nullptr); // if USE_COREGRAPHICS_RENDERING is set, the CoreGraphicsImage class should have been used.
|
||||
|
||||
CGContextDrawImage (cgImage->context, convertToCGRect (image.getBounds()), loadedImage);
|
||||
CGContextFlush (cgImage->context);
|
||||
|
||||
#if ! JUCE_IOS
|
||||
CFRelease (loadedImage);
|
||||
#endif
|
||||
|
||||
// Because it's impossible to create a truly 24-bit CG image, this flag allows a user
|
||||
// to find out whether the file they just loaded the image from had an alpha channel or not.
|
||||
image.getProperties()->set ("originalImageHadAlpha", hasAlphaChan);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Image();
|
||||
}
|
||||
#endif
|
||||
|
||||
Image juce_createImageFromCIImage (CIImage*, int, int);
|
||||
Image juce_createImageFromCIImage (CIImage* im, int w, int h)
|
||||
{
|
||||
CoreGraphicsImage* cgImage = new CoreGraphicsImage (Image::ARGB, w, h, false);
|
||||
|
||||
CIContext* cic = [CIContext contextWithCGContext: cgImage->context options: nil];
|
||||
[cic drawImage: im inRect: CGRectMake (0, 0, w, h) fromRect: CGRectMake (0, 0, w, h)];
|
||||
CGContextFlush (cgImage->context);
|
||||
|
||||
return Image (cgImage);
|
||||
}
|
||||
|
||||
CGImageRef juce_createCoreGraphicsImage (const Image& juceImage, CGColorSpaceRef colourSpace,
|
||||
const bool mustOutliveSource)
|
||||
{
|
||||
return CoreGraphicsImage::createImage (juceImage, colourSpace, mustOutliveSource);
|
||||
}
|
||||
|
||||
CGContextRef juce_getImageContext (const Image& image)
|
||||
{
|
||||
if (CoreGraphicsImage* const cgi = dynamic_cast<CoreGraphicsImage*> (image.getPixelData()))
|
||||
return cgi->context;
|
||||
|
||||
jassertfalse;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if JUCE_IOS
|
||||
Image juce_createImageFromUIImage (UIImage* img)
|
||||
{
|
||||
CGImageRef image = [img CGImage];
|
||||
|
||||
Image retval (Image::ARGB, (int) CGImageGetWidth (image), (int) CGImageGetHeight (image), true);
|
||||
CGContextRef ctx = juce_getImageContext (retval);
|
||||
|
||||
CGContextDrawImage (ctx, CGRectMake (0.0f, 0.0f, CGImageGetWidth (image), CGImageGetHeight (image)), image);
|
||||
|
||||
return retval;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JUCE_MAC
|
||||
NSImage* imageToNSImage (const Image& image, float scaleFactor)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
NSImage* im = [[NSImage alloc] init];
|
||||
auto requiredSize = NSMakeSize (image.getWidth() / scaleFactor, image.getHeight() / scaleFactor);
|
||||
|
||||
[im setSize: requiredSize];
|
||||
CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
|
||||
CGImageRef imageRef = juce_createCoreGraphicsImage (image, colourSpace, true);
|
||||
CGColorSpaceRelease (colourSpace);
|
||||
|
||||
NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage: imageRef];
|
||||
[imageRep setSize: requiredSize];
|
||||
[im addRepresentation: imageRep];
|
||||
[imageRep release];
|
||||
CGImageRelease (imageRef);
|
||||
return im;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
69
modules/juce_graphics/native/juce_mac_CoreGraphicsHelpers.h
Normal file
69
modules/juce_graphics/native/juce_mac_CoreGraphicsHelpers.h
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
namespace
|
||||
{
|
||||
template <class RectType>
|
||||
Rectangle<int> convertToRectInt (RectType r) noexcept
|
||||
{
|
||||
return Rectangle<int> ((int) r.origin.x, (int) r.origin.y, (int) r.size.width, (int) r.size.height);
|
||||
}
|
||||
|
||||
template <class RectType>
|
||||
Rectangle<float> convertToRectFloat (RectType r) noexcept
|
||||
{
|
||||
return Rectangle<float> (r.origin.x, r.origin.y, r.size.width, r.size.height);
|
||||
}
|
||||
|
||||
template <class RectType>
|
||||
CGRect convertToCGRect (RectType r) noexcept
|
||||
{
|
||||
return CGRectMake ((CGFloat) r.getX(), (CGFloat) r.getY(), (CGFloat) r.getWidth(), (CGFloat) r.getHeight());
|
||||
}
|
||||
|
||||
template <typename PointType>
|
||||
CGPoint convertToCGPoint (PointType p) noexcept
|
||||
{
|
||||
return CGPointMake ((CGFloat) p.x, (CGFloat) p.y);
|
||||
}
|
||||
}
|
||||
|
||||
CGImageRef juce_createCoreGraphicsImage (const Image&, CGColorSpaceRef, bool mustOutliveSource);
|
||||
CGContextRef juce_getImageContext (const Image&);
|
||||
|
||||
#if JUCE_IOS
|
||||
Image juce_createImageFromUIImage (UIImage*);
|
||||
#endif
|
||||
|
||||
#if JUCE_MAC
|
||||
NSImage* imageToNSImage (const Image& image, float scaleFactor = 1.0f);
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
856
modules/juce_graphics/native/juce_mac_Fonts.mm
Normal file
856
modules/juce_graphics/native/juce_mac_Fonts.mm
Normal file
@ -0,0 +1,856 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
static constexpr float referenceFontSize = 1024.0f;
|
||||
|
||||
static CTFontRef getCTFontFromTypeface (const Font&);
|
||||
|
||||
namespace CoreTextTypeLayout
|
||||
{
|
||||
static String findBestAvailableStyle (const Font& font, CGAffineTransform& requiredTransform)
|
||||
{
|
||||
auto availableStyles = Font::findAllTypefaceStyles (font.getTypefaceName());
|
||||
auto style = font.getTypefaceStyle();
|
||||
|
||||
if (! availableStyles.contains (style))
|
||||
{
|
||||
if (font.isItalic()) // Fake-up an italic font if there isn't a real one.
|
||||
requiredTransform = CGAffineTransformMake (1.0f, 0, 0.25f, 1.0f, 0, 0);
|
||||
|
||||
return availableStyles[0];
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
static float getFontTotalHeight (CTFontRef font)
|
||||
{
|
||||
return std::abs ((float) CTFontGetAscent (font))
|
||||
+ std::abs ((float) CTFontGetDescent (font));
|
||||
}
|
||||
|
||||
static float getHeightToPointsFactor (CTFontRef font)
|
||||
{
|
||||
return referenceFontSize / getFontTotalHeight (font);
|
||||
}
|
||||
|
||||
static CTFontRef getFontWithPointSize (CTFontRef font, float size)
|
||||
{
|
||||
auto newFont = CTFontCreateCopyWithAttributes (font, size, nullptr, nullptr);
|
||||
CFRelease (font);
|
||||
return newFont;
|
||||
}
|
||||
|
||||
static CTFontRef createCTFont (const Font& font, const float fontSizePoints, CGAffineTransform& transformRequired)
|
||||
{
|
||||
auto cfFontFamily = FontStyleHelpers::getConcreteFamilyName (font).toCFString();
|
||||
auto cfFontStyle = findBestAvailableStyle (font, transformRequired).toCFString();
|
||||
CFStringRef keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute };
|
||||
CFTypeRef values[] = { cfFontFamily, cfFontStyle };
|
||||
|
||||
auto fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys,
|
||||
(const void**) &values,
|
||||
numElementsInArray (keys),
|
||||
&kCFTypeDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
CFRelease (cfFontStyle);
|
||||
|
||||
auto ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes);
|
||||
CFRelease (fontDescAttributes);
|
||||
|
||||
auto ctFontRef = CTFontCreateWithFontDescriptor (ctFontDescRef, fontSizePoints, nullptr);
|
||||
CFRelease (ctFontDescRef);
|
||||
CFRelease (cfFontFamily);
|
||||
|
||||
return ctFontRef;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct Advances
|
||||
{
|
||||
Advances (CTRunRef run, CFIndex numGlyphs) : advances (CTRunGetAdvancesPtr (run))
|
||||
{
|
||||
if (advances == nullptr)
|
||||
{
|
||||
local.malloc (numGlyphs);
|
||||
CTRunGetAdvances (run, CFRangeMake (0, 0), local);
|
||||
advances = local;
|
||||
}
|
||||
}
|
||||
|
||||
const CGSize* advances;
|
||||
HeapBlock<CGSize> local;
|
||||
};
|
||||
|
||||
struct Glyphs
|
||||
{
|
||||
Glyphs (CTRunRef run, size_t numGlyphs) : glyphs (CTRunGetGlyphsPtr (run))
|
||||
{
|
||||
if (glyphs == nullptr)
|
||||
{
|
||||
local.malloc (numGlyphs);
|
||||
CTRunGetGlyphs (run, CFRangeMake (0, 0), local);
|
||||
glyphs = local;
|
||||
}
|
||||
}
|
||||
|
||||
const CGGlyph* glyphs;
|
||||
HeapBlock<CGGlyph> local;
|
||||
};
|
||||
|
||||
struct Positions
|
||||
{
|
||||
Positions (CTRunRef run, size_t numGlyphs) : points (CTRunGetPositionsPtr (run))
|
||||
{
|
||||
if (points == nullptr)
|
||||
{
|
||||
local.malloc (numGlyphs);
|
||||
CTRunGetPositions (run, CFRangeMake (0, 0), local);
|
||||
points = local;
|
||||
}
|
||||
}
|
||||
|
||||
const CGPoint* points;
|
||||
HeapBlock<CGPoint> local;
|
||||
};
|
||||
|
||||
struct LineInfo
|
||||
{
|
||||
LineInfo (CTFrameRef frame, CTLineRef line, CFIndex lineIndex)
|
||||
{
|
||||
CTFrameGetLineOrigins (frame, CFRangeMake (lineIndex, 1), &origin);
|
||||
CTLineGetTypographicBounds (line, &ascent, &descent, &leading);
|
||||
}
|
||||
|
||||
CGPoint origin;
|
||||
CGFloat ascent, descent, leading;
|
||||
};
|
||||
|
||||
static CTFontRef getOrCreateFont (const Font& f)
|
||||
{
|
||||
if (auto ctf = getCTFontFromTypeface (f))
|
||||
{
|
||||
CFRetain (ctf);
|
||||
return ctf;
|
||||
}
|
||||
|
||||
CGAffineTransform transform;
|
||||
return createCTFont (f, referenceFontSize, transform);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static CTTextAlignment getTextAlignment (const AttributedString& text)
|
||||
{
|
||||
switch (text.getJustification().getOnlyHorizontalFlags())
|
||||
{
|
||||
#if defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
|
||||
case Justification::right: return kCTTextAlignmentRight;
|
||||
case Justification::horizontallyCentred: return kCTTextAlignmentCenter;
|
||||
case Justification::horizontallyJustified: return kCTTextAlignmentJustified;
|
||||
default: return kCTTextAlignmentLeft;
|
||||
#else
|
||||
case Justification::right: return kCTRightTextAlignment;
|
||||
case Justification::horizontallyCentred: return kCTCenterTextAlignment;
|
||||
case Justification::horizontallyJustified: return kCTJustifiedTextAlignment;
|
||||
default: return kCTLeftTextAlignment;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static CTLineBreakMode getLineBreakMode (const AttributedString& text)
|
||||
{
|
||||
switch (text.getWordWrap())
|
||||
{
|
||||
case AttributedString::none: return kCTLineBreakByClipping;
|
||||
case AttributedString::byChar: return kCTLineBreakByCharWrapping;
|
||||
default: return kCTLineBreakByWordWrapping;
|
||||
}
|
||||
}
|
||||
|
||||
static CTWritingDirection getWritingDirection (const AttributedString& text)
|
||||
{
|
||||
switch (text.getReadingDirection())
|
||||
{
|
||||
case AttributedString::rightToLeft: return kCTWritingDirectionRightToLeft;
|
||||
case AttributedString::leftToRight: return kCTWritingDirectionLeftToRight;
|
||||
default: return kCTWritingDirectionNatural;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static CFAttributedStringRef createCFAttributedString (const AttributedString& text)
|
||||
{
|
||||
#if JUCE_IOS
|
||||
auto rgbColourSpace = CGColorSpaceCreateDeviceRGB();
|
||||
#endif
|
||||
|
||||
auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0);
|
||||
auto cfText = text.getText().toCFString();
|
||||
CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText);
|
||||
CFRelease (cfText);
|
||||
|
||||
auto numCharacterAttributes = text.getNumAttributes();
|
||||
auto attribStringLen = CFAttributedStringGetLength (attribString);
|
||||
|
||||
for (int i = 0; i < numCharacterAttributes; ++i)
|
||||
{
|
||||
auto& attr = text.getAttribute (i);
|
||||
auto rangeStart = attr.range.getStart();
|
||||
|
||||
if (rangeStart >= attribStringLen)
|
||||
continue;
|
||||
|
||||
auto range = CFRangeMake (rangeStart, jmin (attr.range.getEnd(), (int) attribStringLen) - rangeStart);
|
||||
|
||||
if (auto ctFontRef = getOrCreateFont (attr.font))
|
||||
{
|
||||
ctFontRef = getFontWithPointSize (ctFontRef, attr.font.getHeight() * getHeightToPointsFactor (ctFontRef));
|
||||
CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef);
|
||||
|
||||
auto extraKerning = attr.font.getExtraKerningFactor();
|
||||
|
||||
if (extraKerning != 0)
|
||||
{
|
||||
extraKerning *= attr.font.getHeight();
|
||||
|
||||
auto numberRef = CFNumberCreate (0, kCFNumberFloatType, &extraKerning);
|
||||
CFAttributedStringSetAttribute (attribString, range, kCTKernAttributeName, numberRef);
|
||||
CFRelease (numberRef);
|
||||
}
|
||||
|
||||
CFRelease (ctFontRef);
|
||||
}
|
||||
|
||||
{
|
||||
auto col = attr.colour;
|
||||
|
||||
#if JUCE_IOS
|
||||
const CGFloat components[] = { col.getFloatRed(),
|
||||
col.getFloatGreen(),
|
||||
col.getFloatBlue(),
|
||||
col.getFloatAlpha() };
|
||||
auto colour = CGColorCreate (rgbColourSpace, components);
|
||||
#else
|
||||
auto colour = CGColorCreateGenericRGB (col.getFloatRed(),
|
||||
col.getFloatGreen(),
|
||||
col.getFloatBlue(),
|
||||
col.getFloatAlpha());
|
||||
#endif
|
||||
|
||||
CFAttributedStringSetAttribute (attribString, range, kCTForegroundColorAttributeName, colour);
|
||||
CGColorRelease (colour);
|
||||
}
|
||||
}
|
||||
|
||||
// Paragraph Attributes
|
||||
auto ctTextAlignment = getTextAlignment (text);
|
||||
auto ctLineBreakMode = getLineBreakMode (text);
|
||||
auto ctWritingDirection = getWritingDirection (text);
|
||||
CGFloat ctLineSpacing = text.getLineSpacing();
|
||||
|
||||
CTParagraphStyleSetting settings[] =
|
||||
{
|
||||
{ kCTParagraphStyleSpecifierAlignment, sizeof (CTTextAlignment), &ctTextAlignment },
|
||||
{ kCTParagraphStyleSpecifierLineBreakMode, sizeof (CTLineBreakMode), &ctLineBreakMode },
|
||||
{ kCTParagraphStyleSpecifierBaseWritingDirection, sizeof (CTWritingDirection), &ctWritingDirection},
|
||||
{ kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &ctLineSpacing }
|
||||
};
|
||||
|
||||
auto ctParagraphStyleRef = CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings));
|
||||
CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)),
|
||||
kCTParagraphStyleAttributeName, ctParagraphStyleRef);
|
||||
CFRelease (ctParagraphStyleRef);
|
||||
#if JUCE_IOS
|
||||
CGColorSpaceRelease (rgbColourSpace);
|
||||
#endif
|
||||
return attribString;
|
||||
}
|
||||
|
||||
static CTFrameRef createCTFrame (const AttributedString& text, CGRect bounds)
|
||||
{
|
||||
auto attribString = createCFAttributedString (text);
|
||||
auto framesetter = CTFramesetterCreateWithAttributedString (attribString);
|
||||
CFRelease (attribString);
|
||||
|
||||
auto path = CGPathCreateMutable();
|
||||
CGPathAddRect (path, nullptr, bounds);
|
||||
|
||||
auto frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr);
|
||||
CFRelease (framesetter);
|
||||
CGPathRelease (path);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
static Range<float> getLineVerticalRange (CTFrameRef frame, CFArrayRef lines, int lineIndex)
|
||||
{
|
||||
LineInfo info (frame, (CTLineRef) CFArrayGetValueAtIndex (lines, lineIndex), lineIndex);
|
||||
|
||||
return { (float) (info.origin.y - info.descent),
|
||||
(float) (info.origin.y + info.ascent) };
|
||||
}
|
||||
|
||||
static float findCTFrameHeight (CTFrameRef frame)
|
||||
{
|
||||
auto lines = CTFrameGetLines (frame);
|
||||
auto numLines = CFArrayGetCount (lines);
|
||||
|
||||
if (numLines == 0)
|
||||
return 0;
|
||||
|
||||
auto range = getLineVerticalRange (frame, lines, 0);
|
||||
|
||||
if (numLines > 1)
|
||||
range = range.getUnionWith (getLineVerticalRange (frame, lines, (int) numLines - 1));
|
||||
|
||||
return range.getLength();
|
||||
}
|
||||
|
||||
static void drawToCGContext (const AttributedString& text, const Rectangle<float>& area,
|
||||
const CGContextRef& context, float flipHeight)
|
||||
{
|
||||
Rectangle<float> ctFrameArea;
|
||||
auto verticalJustification = text.getJustification().getOnlyVerticalFlags();
|
||||
|
||||
// Ugly hack to fix a bug in OS X Sierra where the CTFrame needs to be slightly
|
||||
// larger than the font height - otherwise the CTFrame will be invalid
|
||||
if (verticalJustification == Justification::verticallyCentred)
|
||||
ctFrameArea = area.withSizeKeepingCentre (area.getWidth(), area.getHeight() * 1.1f);
|
||||
else if (verticalJustification == Justification::bottom)
|
||||
ctFrameArea = area.withTop (area.getY() - (area.getHeight() * 0.1f));
|
||||
else
|
||||
ctFrameArea = area.withHeight (area.getHeight() * 1.1f);
|
||||
|
||||
auto frame = createCTFrame (text, CGRectMake ((CGFloat) ctFrameArea.getX(), flipHeight - (CGFloat) ctFrameArea.getBottom(),
|
||||
(CGFloat) ctFrameArea.getWidth(), (CGFloat) ctFrameArea.getHeight()));
|
||||
|
||||
if (verticalJustification == Justification::verticallyCentred
|
||||
|| verticalJustification == Justification::bottom)
|
||||
{
|
||||
auto adjust = ctFrameArea.getHeight() - findCTFrameHeight (frame);
|
||||
|
||||
if (verticalJustification == Justification::verticallyCentred)
|
||||
adjust *= 0.5f;
|
||||
|
||||
CGContextSaveGState (context);
|
||||
CGContextTranslateCTM (context, 0, -adjust);
|
||||
CTFrameDraw (frame, context);
|
||||
CGContextRestoreGState (context);
|
||||
}
|
||||
else
|
||||
{
|
||||
CTFrameDraw (frame, context);
|
||||
}
|
||||
|
||||
CFRelease (frame);
|
||||
}
|
||||
|
||||
static void createLayout (TextLayout& glyphLayout, const AttributedString& text)
|
||||
{
|
||||
auto boundsHeight = glyphLayout.getHeight();
|
||||
auto frame = createCTFrame (text, CGRectMake (0, 0, glyphLayout.getWidth(), boundsHeight));
|
||||
auto lines = CTFrameGetLines (frame);
|
||||
auto numLines = CFArrayGetCount (lines);
|
||||
|
||||
glyphLayout.ensureStorageAllocated ((int) numLines);
|
||||
|
||||
for (CFIndex i = 0; i < numLines; ++i)
|
||||
{
|
||||
auto line = (CTLineRef) CFArrayGetValueAtIndex (lines, i);
|
||||
auto runs = CTLineGetGlyphRuns (line);
|
||||
auto numRuns = CFArrayGetCount (runs);
|
||||
|
||||
auto cfrlineStringRange = CTLineGetStringRange (line);
|
||||
auto lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length - 1;
|
||||
Range<int> lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd);
|
||||
|
||||
LineInfo lineInfo (frame, line, i);
|
||||
|
||||
auto glyphLine = new TextLayout::Line (lineStringRange,
|
||||
Point<float> ((float) lineInfo.origin.x,
|
||||
(float) (boundsHeight - lineInfo.origin.y)),
|
||||
(float) lineInfo.ascent,
|
||||
(float) lineInfo.descent,
|
||||
(float) lineInfo.leading,
|
||||
(int) numRuns);
|
||||
glyphLayout.addLine (glyphLine);
|
||||
|
||||
for (CFIndex j = 0; j < numRuns; ++j)
|
||||
{
|
||||
auto run = (CTRunRef) CFArrayGetValueAtIndex (runs, j);
|
||||
auto numGlyphs = CTRunGetGlyphCount (run);
|
||||
auto runStringRange = CTRunGetStringRange (run);
|
||||
|
||||
auto glyphRun = new TextLayout::Run (Range<int> ((int) runStringRange.location,
|
||||
(int) (runStringRange.location + runStringRange.length - 1)),
|
||||
(int) numGlyphs);
|
||||
glyphLine->runs.add (glyphRun);
|
||||
|
||||
CFDictionaryRef runAttributes = CTRunGetAttributes (run);
|
||||
|
||||
CTFontRef ctRunFont;
|
||||
if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void**) &ctRunFont))
|
||||
{
|
||||
auto cfsFontName = CTFontCopyPostScriptName (ctRunFont);
|
||||
auto ctFontRef = CTFontCreateWithName (cfsFontName, referenceFontSize, nullptr);
|
||||
CFRelease (cfsFontName);
|
||||
|
||||
auto fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef);
|
||||
CFRelease (ctFontRef);
|
||||
|
||||
auto cfsFontFamily = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute);
|
||||
auto cfsFontStyle = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontStyleNameAttribute);
|
||||
|
||||
glyphRun->font = Font (String::fromCFString (cfsFontFamily),
|
||||
String::fromCFString (cfsFontStyle),
|
||||
(float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor));
|
||||
|
||||
CFRelease (cfsFontStyle);
|
||||
CFRelease (cfsFontFamily);
|
||||
}
|
||||
|
||||
CGColorRef cgRunColor;
|
||||
|
||||
if (CFDictionaryGetValueIfPresent (runAttributes, kCTForegroundColorAttributeName, (const void**) &cgRunColor)
|
||||
&& CGColorGetNumberOfComponents (cgRunColor) == 4)
|
||||
{
|
||||
auto* components = CGColorGetComponents (cgRunColor);
|
||||
|
||||
glyphRun->colour = Colour::fromFloatRGBA ((float) components[0],
|
||||
(float) components[1],
|
||||
(float) components[2],
|
||||
(float) components[3]);
|
||||
}
|
||||
|
||||
const Glyphs glyphs (run, (size_t) numGlyphs);
|
||||
const Advances advances (run, numGlyphs);
|
||||
const Positions positions (run, (size_t) numGlyphs);
|
||||
|
||||
for (CFIndex k = 0; k < numGlyphs; ++k)
|
||||
glyphRun->glyphs.add (TextLayout::Glyph (glyphs.glyphs[k], Point<float> ((float) positions.points[k].x,
|
||||
(float) positions.points[k].y),
|
||||
(float) advances.advances[k].width));
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease (frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class OSXTypeface : public Typeface
|
||||
{
|
||||
public:
|
||||
OSXTypeface (const Font& font)
|
||||
: Typeface (font.getTypefaceName(), font.getTypefaceStyle()), canBeUsedForLayout (true)
|
||||
{
|
||||
ctFontRef = CoreTextTypeLayout::createCTFont (font, referenceFontSize, renderingTransform);
|
||||
|
||||
if (ctFontRef != nullptr)
|
||||
{
|
||||
fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr);
|
||||
initialiseMetrics();
|
||||
}
|
||||
}
|
||||
|
||||
OSXTypeface (const void* data, size_t dataSize)
|
||||
: Typeface ({}, {}), canBeUsedForLayout (false), dataCopy (data, dataSize)
|
||||
{
|
||||
// We can't use CFDataCreate here as this triggers a false positive in ASAN
|
||||
// so copy the data manually and use CFDataCreateWithBytesNoCopy
|
||||
auto cfData = CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) dataCopy.getData(),
|
||||
(CFIndex) dataCopy.getSize(), kCFAllocatorNull);
|
||||
auto provider = CGDataProviderCreateWithCFData (cfData);
|
||||
CFRelease (cfData);
|
||||
|
||||
#if JUCE_IOS
|
||||
// Workaround for a an obscure iOS bug which can cause the app to dead-lock
|
||||
// when loading custom type faces. See: http://www.openradar.me/18778790 and
|
||||
// http://stackoverflow.com/questions/40242370/app-hangs-in-simulator
|
||||
[UIFont systemFontOfSize: 12];
|
||||
#endif
|
||||
|
||||
fontRef = CGFontCreateWithDataProvider (provider);
|
||||
CGDataProviderRelease (provider);
|
||||
|
||||
if (fontRef != nullptr)
|
||||
{
|
||||
#if JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8
|
||||
canBeUsedForLayout = CTFontManagerRegisterGraphicsFont (fontRef, nullptr);
|
||||
#endif
|
||||
|
||||
ctFontRef = CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr);
|
||||
|
||||
if (ctFontRef != nullptr)
|
||||
{
|
||||
if (auto fontName = CTFontCopyName (ctFontRef, kCTFontFamilyNameKey))
|
||||
{
|
||||
name = String::fromCFString (fontName);
|
||||
CFRelease (fontName);
|
||||
}
|
||||
|
||||
if (auto fontStyle = CTFontCopyName (ctFontRef, kCTFontStyleNameKey))
|
||||
{
|
||||
style = String::fromCFString (fontStyle);
|
||||
CFRelease (fontStyle);
|
||||
}
|
||||
|
||||
initialiseMetrics();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void initialiseMetrics()
|
||||
{
|
||||
auto ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef));
|
||||
auto ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef));
|
||||
auto ctTotalHeight = ctAscent + ctDescent;
|
||||
|
||||
ascent = ctAscent / ctTotalHeight;
|
||||
unitsToHeightScaleFactor = 1.0f / ctTotalHeight;
|
||||
pathTransform = AffineTransform::scale (unitsToHeightScaleFactor);
|
||||
|
||||
fontHeightToPointsFactor = referenceFontSize / ctTotalHeight;
|
||||
|
||||
const short zero = 0;
|
||||
auto numberRef = CFNumberCreate (0, kCFNumberShortType, &zero);
|
||||
|
||||
CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName };
|
||||
CFTypeRef values[] = { ctFontRef, numberRef };
|
||||
attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys,
|
||||
(const void**) &values, numElementsInArray (keys),
|
||||
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
CFRelease (numberRef);
|
||||
}
|
||||
|
||||
~OSXTypeface()
|
||||
{
|
||||
if (attributedStringAtts != nullptr)
|
||||
CFRelease (attributedStringAtts);
|
||||
|
||||
if (fontRef != nullptr)
|
||||
{
|
||||
#if JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8
|
||||
CTFontManagerUnregisterGraphicsFont (fontRef, nullptr);
|
||||
#endif
|
||||
|
||||
CGFontRelease (fontRef);
|
||||
}
|
||||
|
||||
if (ctFontRef != nullptr)
|
||||
CFRelease (ctFontRef);
|
||||
}
|
||||
|
||||
float getAscent() const override { return ascent; }
|
||||
float getDescent() const override { return 1.0f - ascent; }
|
||||
float getHeightToPointsFactor() const override { return fontHeightToPointsFactor; }
|
||||
|
||||
float getStringWidth (const String& text) override
|
||||
{
|
||||
float x = 0;
|
||||
|
||||
if (ctFontRef != nullptr && text.isNotEmpty())
|
||||
{
|
||||
auto cfText = text.toCFString();
|
||||
auto attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts);
|
||||
CFRelease (cfText);
|
||||
|
||||
auto line = CTLineCreateWithAttributedString (attribString);
|
||||
auto runArray = CTLineGetGlyphRuns (line);
|
||||
|
||||
for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
|
||||
{
|
||||
auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
|
||||
auto length = CTRunGetGlyphCount (run);
|
||||
|
||||
const CoreTextTypeLayout::Advances advances (run, length);
|
||||
|
||||
for (int j = 0; j < length; ++j)
|
||||
x += (float) advances.advances[j].width;
|
||||
}
|
||||
|
||||
CFRelease (line);
|
||||
CFRelease (attribString);
|
||||
|
||||
x *= unitsToHeightScaleFactor;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets) override
|
||||
{
|
||||
xOffsets.add (0);
|
||||
|
||||
if (ctFontRef != nullptr && text.isNotEmpty())
|
||||
{
|
||||
float x = 0;
|
||||
|
||||
auto cfText = text.toCFString();
|
||||
auto attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts);
|
||||
CFRelease (cfText);
|
||||
|
||||
auto line = CTLineCreateWithAttributedString (attribString);
|
||||
auto runArray = CTLineGetGlyphRuns (line);
|
||||
|
||||
for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
|
||||
{
|
||||
auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
|
||||
auto length = CTRunGetGlyphCount (run);
|
||||
|
||||
const CoreTextTypeLayout::Advances advances (run, length);
|
||||
const CoreTextTypeLayout::Glyphs glyphs (run, (size_t) length);
|
||||
|
||||
for (int j = 0; j < length; ++j)
|
||||
{
|
||||
x += (float) advances.advances[j].width;
|
||||
xOffsets.add (x * unitsToHeightScaleFactor);
|
||||
resultGlyphs.add (glyphs.glyphs[j]);
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease (line);
|
||||
CFRelease (attribString);
|
||||
}
|
||||
}
|
||||
|
||||
bool getOutlineForGlyph (int glyphNumber, Path& path) override
|
||||
{
|
||||
jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty
|
||||
|
||||
if (auto pathRef = CTFontCreatePathForGlyph (ctFontRef, (CGGlyph) glyphNumber, &renderingTransform))
|
||||
{
|
||||
CGPathApply (pathRef, &path, pathApplier);
|
||||
CFRelease (pathRef);
|
||||
|
||||
if (! pathTransform.isIdentity())
|
||||
path.applyTransform (pathTransform);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
CGFontRef fontRef = {};
|
||||
CTFontRef ctFontRef = {};
|
||||
|
||||
float fontHeightToPointsFactor = 1.0f;
|
||||
CGAffineTransform renderingTransform = CGAffineTransformIdentity;
|
||||
|
||||
bool canBeUsedForLayout;
|
||||
|
||||
private:
|
||||
MemoryBlock dataCopy;
|
||||
CFDictionaryRef attributedStringAtts = {};
|
||||
float ascent = 0, unitsToHeightScaleFactor = 0;
|
||||
AffineTransform pathTransform;
|
||||
|
||||
static void pathApplier (void* info, const CGPathElement* element)
|
||||
{
|
||||
auto& path = *static_cast<Path*> (info);
|
||||
auto* p = element->points;
|
||||
|
||||
switch (element->type)
|
||||
{
|
||||
case kCGPathElementMoveToPoint: path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break;
|
||||
case kCGPathElementAddLineToPoint: path.lineTo ((float) p[0].x, (float) -p[0].y); break;
|
||||
case kCGPathElementAddQuadCurveToPoint: path.quadraticTo ((float) p[0].x, (float) -p[0].y,
|
||||
(float) p[1].x, (float) -p[1].y); break;
|
||||
case kCGPathElementAddCurveToPoint: path.cubicTo ((float) p[0].x, (float) -p[0].y,
|
||||
(float) p[1].x, (float) -p[1].y,
|
||||
(float) p[2].x, (float) -p[2].y); break;
|
||||
case kCGPathElementCloseSubpath: path.closeSubPath(); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXTypeface)
|
||||
};
|
||||
|
||||
CTFontRef getCTFontFromTypeface (const Font& f)
|
||||
{
|
||||
if (auto* tf = dynamic_cast<OSXTypeface*> (f.getTypeface()))
|
||||
return tf->ctFontRef;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
StringArray Font::findAllTypefaceNames()
|
||||
{
|
||||
StringArray names;
|
||||
|
||||
#if JUCE_MAC
|
||||
// CTFontManager only exists on OS X 10.6 and later, it does not exist on iOS
|
||||
auto fontFamilyArray = CTFontManagerCopyAvailableFontFamilyNames();
|
||||
|
||||
for (CFIndex i = 0; i < CFArrayGetCount (fontFamilyArray); ++i)
|
||||
{
|
||||
auto family = String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (fontFamilyArray, i));
|
||||
|
||||
if (! family.startsWithChar ('.')) // ignore fonts that start with a '.'
|
||||
names.addIfNotAlreadyThere (family);
|
||||
}
|
||||
|
||||
CFRelease (fontFamilyArray);
|
||||
#else
|
||||
auto fontCollectionRef = CTFontCollectionCreateFromAvailableFonts (nullptr);
|
||||
auto fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef);
|
||||
CFRelease (fontCollectionRef);
|
||||
|
||||
for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i)
|
||||
{
|
||||
auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i);
|
||||
auto cfsFontFamily = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontFamilyNameAttribute);
|
||||
|
||||
names.addIfNotAlreadyThere (String::fromCFString (cfsFontFamily));
|
||||
|
||||
CFRelease (cfsFontFamily);
|
||||
}
|
||||
|
||||
CFRelease (fontDescriptorArray);
|
||||
#endif
|
||||
|
||||
names.sort (true);
|
||||
return names;
|
||||
}
|
||||
|
||||
StringArray Font::findAllTypefaceStyles (const String& family)
|
||||
{
|
||||
if (FontStyleHelpers::isPlaceholderFamilyName (family))
|
||||
return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));
|
||||
|
||||
StringArray results;
|
||||
|
||||
auto cfsFontFamily = family.toCFString();
|
||||
CFStringRef keys[] = { kCTFontFamilyNameAttribute };
|
||||
CFTypeRef values[] = { cfsFontFamily };
|
||||
|
||||
auto fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys),
|
||||
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
CFRelease (cfsFontFamily);
|
||||
|
||||
auto ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes);
|
||||
CFRelease (fontDescAttributes);
|
||||
|
||||
auto fontFamilyArray = CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks);
|
||||
CFRelease (ctFontDescRef);
|
||||
|
||||
auto fontCollectionRef = CTFontCollectionCreateWithFontDescriptors (fontFamilyArray, nullptr);
|
||||
CFRelease (fontFamilyArray);
|
||||
|
||||
auto fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef);
|
||||
CFRelease (fontCollectionRef);
|
||||
|
||||
if (fontDescriptorArray != nullptr)
|
||||
{
|
||||
for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i)
|
||||
{
|
||||
auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i);
|
||||
auto cfsFontStyle = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontStyleNameAttribute);
|
||||
results.add (String::fromCFString (cfsFontStyle));
|
||||
CFRelease (cfsFontStyle);
|
||||
}
|
||||
|
||||
CFRelease (fontDescriptorArray);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { return new OSXTypeface (font); }
|
||||
Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size) { return new OSXTypeface (data, size); }
|
||||
|
||||
void Typeface::scanFolderForFonts (const File&)
|
||||
{
|
||||
jassertfalse; // not implemented on this platform
|
||||
}
|
||||
|
||||
struct DefaultFontNames
|
||||
{
|
||||
#if JUCE_IOS
|
||||
String defaultSans { "Helvetica" },
|
||||
defaultSerif { "Times New Roman" },
|
||||
defaultFixed { "Courier New" };
|
||||
#else
|
||||
String defaultSans { "Lucida Grande" },
|
||||
defaultSerif { "Times New Roman" },
|
||||
defaultFixed { "Menlo" };
|
||||
#endif
|
||||
};
|
||||
|
||||
Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
|
||||
{
|
||||
static DefaultFontNames defaultNames;
|
||||
|
||||
auto newFont = font;
|
||||
auto& faceName = font.getTypefaceName();
|
||||
|
||||
if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans);
|
||||
else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif);
|
||||
else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed);
|
||||
|
||||
if (font.getTypefaceStyle() == getDefaultStyle())
|
||||
newFont.setTypefaceStyle ("Regular");
|
||||
|
||||
return Typeface::createSystemTypefaceFor (newFont);
|
||||
}
|
||||
|
||||
static bool canAllTypefacesBeUsedInLayout (const AttributedString& text)
|
||||
{
|
||||
auto numCharacterAttributes = text.getNumAttributes();
|
||||
|
||||
for (int i = 0; i < numCharacterAttributes; ++i)
|
||||
{
|
||||
if (auto tf = dynamic_cast<OSXTypeface*> (text.getAttribute(i).font.getTypeface()))
|
||||
if (tf->canBeUsedForLayout)
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextLayout::createNativeLayout (const AttributedString& text)
|
||||
{
|
||||
if (canAllTypefacesBeUsedInLayout (text))
|
||||
{
|
||||
CoreTextTypeLayout::createLayout (*this, text);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
152
modules/juce_graphics/native/juce_mac_IconHelpers.cpp
Normal file
152
modules/juce_graphics/native/juce_mac_IconHelpers.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
extern Image JUCE_API getIconFromApplication (const String&, int);
|
||||
|
||||
static Image getIconFromIcnsFile (const File& icnsFile, const int size)
|
||||
{
|
||||
FileInputStream stream (icnsFile);
|
||||
|
||||
if (! stream.openedOk())
|
||||
return {};
|
||||
|
||||
const int numHeaderSectionBytes = 4;
|
||||
char headerSection [numHeaderSectionBytes];
|
||||
|
||||
if (stream.read (headerSection, numHeaderSectionBytes) != numHeaderSectionBytes
|
||||
|| headerSection[0] != 'i'
|
||||
|| headerSection[1] != 'c'
|
||||
|| headerSection[2] != 'n'
|
||||
|| headerSection[3] != 's')
|
||||
return {};
|
||||
|
||||
if (stream.read (headerSection, numHeaderSectionBytes) != numHeaderSectionBytes)
|
||||
return {};
|
||||
|
||||
const auto dataSize = juce::ByteOrder::bigEndianInt (headerSection);
|
||||
|
||||
if (dataSize <= 0)
|
||||
return {};
|
||||
|
||||
OwnedArray<juce::ImageFileFormat> internalFormats;
|
||||
internalFormats.add (new PNGImageFormat());
|
||||
internalFormats.add (new JPEGImageFormat());
|
||||
|
||||
Array<Image> images;
|
||||
auto maxWidth = 0;
|
||||
auto maxWidthIndex = -1;
|
||||
|
||||
while (stream.getPosition() < dataSize)
|
||||
{
|
||||
const auto sectionStart = stream.getPosition();
|
||||
|
||||
if (! stream.setPosition (sectionStart + 4))
|
||||
break;
|
||||
|
||||
if (stream.read (headerSection, numHeaderSectionBytes) != numHeaderSectionBytes)
|
||||
break;
|
||||
|
||||
const auto sectionSize = ByteOrder::bigEndianInt (headerSection);
|
||||
|
||||
if (sectionSize <= 0)
|
||||
break;
|
||||
|
||||
const auto sectionDataStart = stream.getPosition();
|
||||
|
||||
for (auto* fmt : internalFormats)
|
||||
{
|
||||
if (fmt->canUnderstand (stream))
|
||||
{
|
||||
stream.setPosition (sectionDataStart);
|
||||
|
||||
images.add (fmt->decodeImage (stream));
|
||||
|
||||
const auto lastImageIndex = images.size() - 1;
|
||||
const auto lastWidth = images.getReference (lastImageIndex).getWidth();
|
||||
|
||||
if (lastWidth > maxWidth)
|
||||
{
|
||||
maxWidthIndex = lastImageIndex;
|
||||
maxWidth = lastWidth;
|
||||
}
|
||||
}
|
||||
|
||||
stream.setPosition (sectionDataStart);
|
||||
}
|
||||
|
||||
stream.setPosition (sectionStart + sectionSize);
|
||||
}
|
||||
|
||||
return maxWidthIndex == -1 ? juce::Image()
|
||||
: images.getReference (maxWidthIndex).rescaled (size, size, Graphics::ResamplingQuality::highResamplingQuality);
|
||||
}
|
||||
|
||||
Image JUCE_API getIconFromApplication (const String& applicationPath, const int size)
|
||||
{
|
||||
Image hostIcon;
|
||||
|
||||
if (CFStringRef pathCFString = CFStringCreateWithCString (kCFAllocatorDefault, applicationPath.toRawUTF8(), kCFStringEncodingUTF8))
|
||||
{
|
||||
if (CFURLRef url = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, pathCFString, kCFURLPOSIXPathStyle, 1))
|
||||
{
|
||||
if (CFBundleRef appBundle = CFBundleCreate (kCFAllocatorDefault, url))
|
||||
{
|
||||
if (CFTypeRef infoValue = CFBundleGetValueForInfoDictionaryKey (appBundle, CFSTR("CFBundleIconFile")))
|
||||
{
|
||||
if (CFGetTypeID (infoValue) == CFStringGetTypeID())
|
||||
{
|
||||
CFStringRef iconFilename = reinterpret_cast<CFStringRef> (infoValue);
|
||||
CFStringRef resourceURLSuffix = CFStringHasSuffix (iconFilename, CFSTR(".icns")) ? nullptr : CFSTR("icns");
|
||||
if (CFURLRef iconURL = CFBundleCopyResourceURL (appBundle, iconFilename, resourceURLSuffix, nullptr))
|
||||
{
|
||||
if (CFStringRef iconPath = CFURLCopyFileSystemPath (iconURL, kCFURLPOSIXPathStyle))
|
||||
{
|
||||
File icnsFile (CFStringGetCStringPtr (iconPath, CFStringGetSystemEncoding()));
|
||||
hostIcon = getIconFromIcnsFile (icnsFile, size);
|
||||
CFRelease (iconPath);
|
||||
}
|
||||
|
||||
CFRelease (iconURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease (appBundle);
|
||||
}
|
||||
|
||||
CFRelease (url);
|
||||
}
|
||||
|
||||
CFRelease (pathCFString);
|
||||
}
|
||||
|
||||
return hostIcon;
|
||||
}
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,830 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
template <typename Type>
|
||||
D2D1_RECT_F rectangleToRectF (const Rectangle<Type>& r)
|
||||
{
|
||||
return D2D1::RectF ((float) r.getX(), (float) r.getY(), (float) r.getRight(), (float) r.getBottom());
|
||||
}
|
||||
|
||||
static D2D1_COLOR_F colourToD2D (Colour c)
|
||||
{
|
||||
return D2D1::ColorF::ColorF (c.getFloatRed(), c.getFloatGreen(), c.getFloatBlue(), c.getFloatAlpha());
|
||||
}
|
||||
|
||||
static void pathToGeometrySink (const Path& path, ID2D1GeometrySink* sink, const AffineTransform& transform)
|
||||
{
|
||||
Path::Iterator it (path);
|
||||
|
||||
while (it.next())
|
||||
{
|
||||
switch (it.elementType)
|
||||
{
|
||||
case Path::Iterator::cubicTo:
|
||||
{
|
||||
D2D1_BEZIER_SEGMENT seg;
|
||||
|
||||
transform.transformPoint (it.x1, it.y1);
|
||||
seg.point1 = D2D1::Point2F (it.x1, it.y1);
|
||||
|
||||
transform.transformPoint (it.x2, it.y2);
|
||||
seg.point2 = D2D1::Point2F (it.x2, it.y2);
|
||||
|
||||
transform.transformPoint (it.x3, it.y3);
|
||||
seg.point3 = D2D1::Point2F (it.x3, it.y3);
|
||||
|
||||
sink->AddBezier (seg);
|
||||
break;
|
||||
}
|
||||
|
||||
case Path::Iterator::lineTo:
|
||||
{
|
||||
transform.transformPoint (it.x1, it.y1);
|
||||
sink->AddLine (D2D1::Point2F (it.x1, it.y1));
|
||||
break;
|
||||
}
|
||||
|
||||
case Path::Iterator::quadraticTo:
|
||||
{
|
||||
D2D1_QUADRATIC_BEZIER_SEGMENT seg;
|
||||
|
||||
transform.transformPoint (it.x1, it.y1);
|
||||
seg.point1 = D2D1::Point2F (it.x1, it.y1);
|
||||
|
||||
transform.transformPoint (it.x2, it.y2);
|
||||
seg.point2 = D2D1::Point2F (it.x2, it.y2);
|
||||
|
||||
sink->AddQuadraticBezier (seg);
|
||||
break;
|
||||
}
|
||||
|
||||
case Path::Iterator::closePath:
|
||||
{
|
||||
sink->EndFigure (D2D1_FIGURE_END_CLOSED);
|
||||
break;
|
||||
}
|
||||
|
||||
case Path::Iterator::startNewSubPath:
|
||||
{
|
||||
transform.transformPoint (it.x1, it.y1);
|
||||
sink->BeginFigure (D2D1::Point2F (it.x1, it.y1), D2D1_FIGURE_BEGIN_FILLED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static D2D1::Matrix3x2F transformToMatrix (const AffineTransform& transform)
|
||||
{
|
||||
D2D1::Matrix3x2F matrix;
|
||||
matrix._11 = transform.mat00;
|
||||
matrix._12 = transform.mat10;
|
||||
matrix._21 = transform.mat01;
|
||||
matrix._22 = transform.mat11;
|
||||
matrix._31 = transform.mat02;
|
||||
matrix._32 = transform.mat12;
|
||||
return matrix;
|
||||
}
|
||||
|
||||
static D2D1_POINT_2F pointTransformed (int x, int y, const AffineTransform& transform)
|
||||
{
|
||||
transform.transformPoint (x, y);
|
||||
return D2D1::Point2F ((FLOAT) x, (FLOAT) y);
|
||||
}
|
||||
|
||||
static void rectToGeometrySink (const Rectangle<int>& rect, ID2D1GeometrySink* sink, const AffineTransform& transform)
|
||||
{
|
||||
sink->BeginFigure (pointTransformed (rect.getX(), rect.getY(), transform), D2D1_FIGURE_BEGIN_FILLED);
|
||||
sink->AddLine (pointTransformed (rect.getRight(), rect.getY(), transform));
|
||||
sink->AddLine (pointTransformed (rect.getRight(), rect.getBottom(), transform));
|
||||
sink->AddLine (pointTransformed (rect.getX(), rect.getBottom(), transform));
|
||||
sink->EndFigure (D2D1_FIGURE_END_CLOSED);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct Direct2DLowLevelGraphicsContext::Pimpl
|
||||
{
|
||||
ID2D1PathGeometry* rectListToPathGeometry (const RectangleList<int>& clipRegion)
|
||||
{
|
||||
ID2D1PathGeometry* p = nullptr;
|
||||
factories->d2dFactory->CreatePathGeometry (&p);
|
||||
|
||||
ComSmartPtr<ID2D1GeometrySink> sink;
|
||||
HRESULT hr = p->Open (sink.resetAndGetPointerAddress()); // xxx handle error
|
||||
sink->SetFillMode (D2D1_FILL_MODE_WINDING);
|
||||
|
||||
for (int i = clipRegion.getNumRectangles(); --i >= 0;)
|
||||
rectToGeometrySink (clipRegion.getRectangle(i), sink, AffineTransform());
|
||||
|
||||
hr = sink->Close();
|
||||
return p;
|
||||
}
|
||||
|
||||
ID2D1PathGeometry* pathToPathGeometry (const Path& path, const AffineTransform& transform)
|
||||
{
|
||||
ID2D1PathGeometry* p = nullptr;
|
||||
factories->d2dFactory->CreatePathGeometry (&p);
|
||||
|
||||
ComSmartPtr<ID2D1GeometrySink> sink;
|
||||
HRESULT hr = p->Open (sink.resetAndGetPointerAddress());
|
||||
sink->SetFillMode (D2D1_FILL_MODE_WINDING); // xxx need to check Path::isUsingNonZeroWinding()
|
||||
|
||||
pathToGeometrySink (path, sink, transform);
|
||||
|
||||
hr = sink->Close();
|
||||
return p;
|
||||
}
|
||||
|
||||
SharedResourcePointer<Direct2DFactories> factories;
|
||||
|
||||
ComSmartPtr<ID2D1HwndRenderTarget> renderingTarget;
|
||||
ComSmartPtr<ID2D1SolidColorBrush> colourBrush;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct Direct2DLowLevelGraphicsContext::SavedState
|
||||
{
|
||||
public:
|
||||
SavedState (Direct2DLowLevelGraphicsContext& owner_)
|
||||
: owner (owner_)
|
||||
{
|
||||
if (owner.currentState != nullptr)
|
||||
{
|
||||
// xxx seems like a very slow way to create one of these, and this is a performance
|
||||
// bottleneck.. Can the same internal objects be shared by multiple state objects, maybe using copy-on-write?
|
||||
setFill (owner.currentState->fillType);
|
||||
currentBrush = owner.currentState->currentBrush;
|
||||
clipRect = owner.currentState->clipRect;
|
||||
transform = owner.currentState->transform;
|
||||
|
||||
font = owner.currentState->font;
|
||||
currentFontFace = owner.currentState->currentFontFace;
|
||||
}
|
||||
else
|
||||
{
|
||||
const D2D1_SIZE_U size (owner.pimpl->renderingTarget->GetPixelSize());
|
||||
clipRect.setSize (size.width, size.height);
|
||||
setFill (FillType (Colours::black));
|
||||
}
|
||||
}
|
||||
|
||||
~SavedState()
|
||||
{
|
||||
clearClip();
|
||||
clearFont();
|
||||
clearFill();
|
||||
clearPathClip();
|
||||
clearImageClip();
|
||||
complexClipLayer = nullptr;
|
||||
bitmapMaskLayer = nullptr;
|
||||
}
|
||||
|
||||
void clearClip()
|
||||
{
|
||||
popClips();
|
||||
shouldClipRect = false;
|
||||
}
|
||||
|
||||
void clipToRectangle (const Rectangle<int>& r)
|
||||
{
|
||||
clearClip();
|
||||
clipRect = r.toFloat().transformed (transform).getSmallestIntegerContainer();
|
||||
shouldClipRect = true;
|
||||
pushClips();
|
||||
}
|
||||
|
||||
void clearPathClip()
|
||||
{
|
||||
popClips();
|
||||
|
||||
if (shouldClipComplex)
|
||||
{
|
||||
complexClipGeometry = nullptr;
|
||||
shouldClipComplex = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::SavedState::clipToPath (ID2D1Geometry* geometry)
|
||||
{
|
||||
clearPathClip();
|
||||
|
||||
if (complexClipLayer == nullptr)
|
||||
owner.pimpl->renderingTarget->CreateLayer (complexClipLayer.resetAndGetPointerAddress());
|
||||
|
||||
complexClipGeometry = geometry;
|
||||
shouldClipComplex = true;
|
||||
pushClips();
|
||||
}
|
||||
|
||||
void clearRectListClip()
|
||||
{
|
||||
popClips();
|
||||
|
||||
if (shouldClipRectList)
|
||||
{
|
||||
rectListGeometry = nullptr;
|
||||
shouldClipRectList = false;
|
||||
}
|
||||
}
|
||||
|
||||
void clipToRectList (ID2D1Geometry* geometry)
|
||||
{
|
||||
clearRectListClip();
|
||||
|
||||
if (rectListLayer == nullptr)
|
||||
owner.pimpl->renderingTarget->CreateLayer (rectListLayer.resetAndGetPointerAddress());
|
||||
|
||||
rectListGeometry = geometry;
|
||||
shouldClipRectList = true;
|
||||
pushClips();
|
||||
}
|
||||
|
||||
void clearImageClip()
|
||||
{
|
||||
popClips();
|
||||
|
||||
if (shouldClipBitmap)
|
||||
{
|
||||
maskBitmap = nullptr;
|
||||
bitmapMaskBrush = nullptr;
|
||||
shouldClipBitmap = false;
|
||||
}
|
||||
}
|
||||
|
||||
void clipToImage (const Image& clipImage, const AffineTransform& clipTransform)
|
||||
{
|
||||
clearImageClip();
|
||||
|
||||
if (bitmapMaskLayer == nullptr)
|
||||
owner.pimpl->renderingTarget->CreateLayer (bitmapMaskLayer.resetAndGetPointerAddress());
|
||||
|
||||
D2D1_BRUSH_PROPERTIES brushProps;
|
||||
brushProps.opacity = 1;
|
||||
brushProps.transform = transformToMatrix (clipTransform);
|
||||
|
||||
D2D1_BITMAP_BRUSH_PROPERTIES bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP);
|
||||
|
||||
D2D1_SIZE_U size;
|
||||
size.width = clipImage.getWidth();
|
||||
size.height = clipImage.getHeight();
|
||||
|
||||
D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties();
|
||||
|
||||
maskImage = clipImage.convertedToFormat (Image::ARGB);
|
||||
Image::BitmapData bd (maskImage, Image::BitmapData::readOnly); // xxx should be maskImage?
|
||||
bp.pixelFormat = owner.pimpl->renderingTarget->GetPixelFormat();
|
||||
bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
|
||||
|
||||
HRESULT hr = owner.pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, maskBitmap.resetAndGetPointerAddress());
|
||||
hr = owner.pimpl->renderingTarget->CreateBitmapBrush (maskBitmap, bmProps, brushProps, bitmapMaskBrush.resetAndGetPointerAddress());
|
||||
|
||||
imageMaskLayerParams = D2D1::LayerParameters();
|
||||
imageMaskLayerParams.opacityBrush = bitmapMaskBrush;
|
||||
|
||||
shouldClipBitmap = true;
|
||||
pushClips();
|
||||
}
|
||||
|
||||
void popClips()
|
||||
{
|
||||
if (clipsBitmap)
|
||||
{
|
||||
owner.pimpl->renderingTarget->PopLayer();
|
||||
clipsBitmap = false;
|
||||
}
|
||||
|
||||
if (clipsComplex)
|
||||
{
|
||||
owner.pimpl->renderingTarget->PopLayer();
|
||||
clipsComplex = false;
|
||||
}
|
||||
|
||||
if (clipsRectList)
|
||||
{
|
||||
owner.pimpl->renderingTarget->PopLayer();
|
||||
clipsRectList = false;
|
||||
}
|
||||
|
||||
if (clipsRect)
|
||||
{
|
||||
owner.pimpl->renderingTarget->PopAxisAlignedClip();
|
||||
clipsRect = false;
|
||||
}
|
||||
}
|
||||
|
||||
void pushClips()
|
||||
{
|
||||
if (shouldClipRect && !clipsRect)
|
||||
{
|
||||
owner.pimpl->renderingTarget->PushAxisAlignedClip (rectangleToRectF (clipRect), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
|
||||
clipsRect = true;
|
||||
}
|
||||
|
||||
if (shouldClipRectList && !clipsRectList)
|
||||
{
|
||||
D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters();
|
||||
rectListGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds);
|
||||
layerParams.geometricMask = rectListGeometry;
|
||||
owner.pimpl->renderingTarget->PushLayer (layerParams, rectListLayer);
|
||||
clipsRectList = true;
|
||||
}
|
||||
|
||||
if (shouldClipComplex && !clipsComplex)
|
||||
{
|
||||
D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters();
|
||||
complexClipGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds);
|
||||
layerParams.geometricMask = complexClipGeometry;
|
||||
owner.pimpl->renderingTarget->PushLayer (layerParams, complexClipLayer);
|
||||
clipsComplex = true;
|
||||
}
|
||||
|
||||
if (shouldClipBitmap && !clipsBitmap)
|
||||
{
|
||||
owner.pimpl->renderingTarget->PushLayer (imageMaskLayerParams, bitmapMaskLayer);
|
||||
clipsBitmap = true;
|
||||
}
|
||||
}
|
||||
|
||||
void setFill (const FillType& newFillType)
|
||||
{
|
||||
if (fillType != newFillType)
|
||||
{
|
||||
fillType = newFillType;
|
||||
clearFill();
|
||||
}
|
||||
}
|
||||
|
||||
void clearFont()
|
||||
{
|
||||
currentFontFace = localFontFace = nullptr;
|
||||
}
|
||||
|
||||
void setFont (const Font& newFont)
|
||||
{
|
||||
if (font != newFont)
|
||||
{
|
||||
font = newFont;
|
||||
clearFont();
|
||||
}
|
||||
}
|
||||
|
||||
void createFont()
|
||||
{
|
||||
if (currentFontFace == nullptr)
|
||||
{
|
||||
WindowsDirectWriteTypeface* typeface = dynamic_cast<WindowsDirectWriteTypeface*> (font.getTypeface());
|
||||
currentFontFace = typeface->getIDWriteFontFace();
|
||||
fontHeightToEmSizeFactor = typeface->getUnitsToHeightScaleFactor();
|
||||
}
|
||||
}
|
||||
|
||||
void setOpacity (float newOpacity)
|
||||
{
|
||||
fillType.setOpacity (newOpacity);
|
||||
|
||||
if (currentBrush != nullptr)
|
||||
currentBrush->SetOpacity (newOpacity);
|
||||
}
|
||||
|
||||
void clearFill()
|
||||
{
|
||||
gradientStops = nullptr;
|
||||
linearGradient = nullptr;
|
||||
radialGradient = nullptr;
|
||||
bitmap = nullptr;
|
||||
bitmapBrush = nullptr;
|
||||
currentBrush = nullptr;
|
||||
}
|
||||
|
||||
void createBrush()
|
||||
{
|
||||
if (currentBrush == nullptr)
|
||||
{
|
||||
if (fillType.isColour())
|
||||
{
|
||||
D2D1_COLOR_F colour = colourToD2D (fillType.colour);
|
||||
owner.pimpl->colourBrush->SetColor (colour);
|
||||
currentBrush = owner.pimpl->colourBrush;
|
||||
}
|
||||
else if (fillType.isTiledImage())
|
||||
{
|
||||
D2D1_BRUSH_PROPERTIES brushProps;
|
||||
brushProps.opacity = fillType.getOpacity();
|
||||
brushProps.transform = transformToMatrix (fillType.transform);
|
||||
|
||||
D2D1_BITMAP_BRUSH_PROPERTIES bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP);
|
||||
|
||||
image = fillType.image;
|
||||
|
||||
D2D1_SIZE_U size;
|
||||
size.width = image.getWidth();
|
||||
size.height = image.getHeight();
|
||||
|
||||
D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties();
|
||||
|
||||
this->image = image.convertedToFormat (Image::ARGB);
|
||||
Image::BitmapData bd (this->image, Image::BitmapData::readOnly);
|
||||
bp.pixelFormat = owner.pimpl->renderingTarget->GetPixelFormat();
|
||||
bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
|
||||
|
||||
HRESULT hr = owner.pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, bitmap.resetAndGetPointerAddress());
|
||||
hr = owner.pimpl->renderingTarget->CreateBitmapBrush (bitmap, bmProps, brushProps, bitmapBrush.resetAndGetPointerAddress());
|
||||
|
||||
currentBrush = bitmapBrush;
|
||||
}
|
||||
else if (fillType.isGradient())
|
||||
{
|
||||
gradientStops = nullptr;
|
||||
|
||||
D2D1_BRUSH_PROPERTIES brushProps;
|
||||
brushProps.opacity = fillType.getOpacity();
|
||||
brushProps.transform = transformToMatrix (fillType.transform.followedBy (transform));
|
||||
|
||||
const int numColors = fillType.gradient->getNumColours();
|
||||
|
||||
HeapBlock<D2D1_GRADIENT_STOP> stops (numColors);
|
||||
|
||||
for (int i = fillType.gradient->getNumColours(); --i >= 0;)
|
||||
{
|
||||
stops[i].color = colourToD2D (fillType.gradient->getColour (i));
|
||||
stops[i].position = (FLOAT) fillType.gradient->getColourPosition (i);
|
||||
}
|
||||
|
||||
owner.pimpl->renderingTarget->CreateGradientStopCollection (stops.getData(), numColors, gradientStops.resetAndGetPointerAddress());
|
||||
|
||||
if (fillType.gradient->isRadial)
|
||||
{
|
||||
radialGradient = nullptr;
|
||||
|
||||
const Point<float> p1 = fillType.gradient->point1;
|
||||
const Point<float> p2 = fillType.gradient->point2;
|
||||
float r = p1.getDistanceFrom(p2);
|
||||
|
||||
D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES props =
|
||||
D2D1::RadialGradientBrushProperties(D2D1::Point2F(p1.x, p1.y),
|
||||
D2D1::Point2F(0, 0),
|
||||
r, r);
|
||||
|
||||
owner.pimpl->renderingTarget->CreateRadialGradientBrush(props, brushProps, gradientStops, radialGradient.resetAndGetPointerAddress());
|
||||
currentBrush = radialGradient;
|
||||
}
|
||||
else
|
||||
{
|
||||
linearGradient = 0;
|
||||
|
||||
const Point<float> p1 = fillType.gradient->point1;
|
||||
const Point<float> p2 = fillType.gradient->point2;
|
||||
|
||||
D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES props =
|
||||
D2D1::LinearGradientBrushProperties(D2D1::Point2F(p1.x, p1.y),
|
||||
D2D1::Point2F(p2.x, p2.y));
|
||||
|
||||
owner.pimpl->renderingTarget->CreateLinearGradientBrush (props, brushProps, gradientStops, linearGradient.resetAndGetPointerAddress());
|
||||
|
||||
currentBrush = linearGradient;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Direct2DLowLevelGraphicsContext& owner;
|
||||
|
||||
AffineTransform transform;
|
||||
|
||||
Font font;
|
||||
float fontHeightToEmSizeFactor = 1.0;
|
||||
|
||||
IDWriteFontFace* currentFontFace = nullptr;
|
||||
ComSmartPtr<IDWriteFontFace> localFontFace;
|
||||
|
||||
Rectangle<int> clipRect;
|
||||
bool clipsRect = false, shouldClipRect = false;
|
||||
|
||||
Image image;
|
||||
ComSmartPtr<ID2D1Bitmap> bitmap; // xxx needs a better name - what is this for??
|
||||
bool clipsBitmap = false, shouldClipBitmap = false;
|
||||
|
||||
ComSmartPtr<ID2D1Geometry> complexClipGeometry;
|
||||
D2D1_LAYER_PARAMETERS complexClipLayerParams;
|
||||
ComSmartPtr<ID2D1Layer> complexClipLayer;
|
||||
bool clipsComplex = false, shouldClipComplex = false;
|
||||
|
||||
ComSmartPtr<ID2D1Geometry> rectListGeometry;
|
||||
D2D1_LAYER_PARAMETERS rectListLayerParams;
|
||||
ComSmartPtr<ID2D1Layer> rectListLayer;
|
||||
bool clipsRectList = false, shouldClipRectList = false;
|
||||
|
||||
Image maskImage;
|
||||
D2D1_LAYER_PARAMETERS imageMaskLayerParams;
|
||||
ComSmartPtr<ID2D1Layer> bitmapMaskLayer;
|
||||
ComSmartPtr<ID2D1Bitmap> maskBitmap;
|
||||
ComSmartPtr<ID2D1BitmapBrush> bitmapMaskBrush;
|
||||
|
||||
ID2D1Brush* currentBrush = nullptr;
|
||||
ComSmartPtr<ID2D1BitmapBrush> bitmapBrush;
|
||||
ComSmartPtr<ID2D1LinearGradientBrush> linearGradient;
|
||||
ComSmartPtr<ID2D1RadialGradientBrush> radialGradient;
|
||||
ComSmartPtr<ID2D1GradientStopCollection> gradientStops;
|
||||
|
||||
FillType fillType;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedState)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
Direct2DLowLevelGraphicsContext::Direct2DLowLevelGraphicsContext (HWND hwnd_)
|
||||
: hwnd (hwnd_),
|
||||
currentState (nullptr),
|
||||
pimpl (new Pimpl())
|
||||
{
|
||||
RECT windowRect;
|
||||
GetClientRect (hwnd, &windowRect);
|
||||
D2D1_SIZE_U size = { (UINT32) (windowRect.right - windowRect.left), (UINT32) (windowRect.bottom - windowRect.top) };
|
||||
bounds.setSize (size.width, size.height);
|
||||
|
||||
D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties();
|
||||
D2D1_HWND_RENDER_TARGET_PROPERTIES propsHwnd = D2D1::HwndRenderTargetProperties (hwnd, size);
|
||||
|
||||
if (pimpl->factories->d2dFactory != nullptr)
|
||||
{
|
||||
HRESULT hr = pimpl->factories->d2dFactory->CreateHwndRenderTarget (props, propsHwnd, pimpl->renderingTarget.resetAndGetPointerAddress());
|
||||
jassert (SUCCEEDED (hr)); ignoreUnused (hr);
|
||||
hr = pimpl->renderingTarget->CreateSolidColorBrush (D2D1::ColorF::ColorF (0.0f, 0.0f, 0.0f, 1.0f), pimpl->colourBrush.resetAndGetPointerAddress());
|
||||
}
|
||||
}
|
||||
|
||||
Direct2DLowLevelGraphicsContext::~Direct2DLowLevelGraphicsContext()
|
||||
{
|
||||
states.clear();
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::resized()
|
||||
{
|
||||
RECT windowRect;
|
||||
GetClientRect (hwnd, &windowRect);
|
||||
D2D1_SIZE_U size = { (UINT32) (windowRect.right - windowRect.left), (UINT32) (windowRect.bottom - windowRect.top) };
|
||||
|
||||
pimpl->renderingTarget->Resize (size);
|
||||
bounds.setSize (size.width, size.height);
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::clear()
|
||||
{
|
||||
pimpl->renderingTarget->Clear (D2D1::ColorF (D2D1::ColorF::White, 0.0f)); // xxx why white and not black?
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::start()
|
||||
{
|
||||
pimpl->renderingTarget->BeginDraw();
|
||||
saveState();
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::end()
|
||||
{
|
||||
states.clear();
|
||||
currentState = 0;
|
||||
pimpl->renderingTarget->EndDraw();
|
||||
pimpl->renderingTarget->CheckWindowState();
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::setOrigin (Point<int> o)
|
||||
{
|
||||
addTransform (AffineTransform::translation ((float) o.x, (float) o.y));
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::addTransform (const AffineTransform& transform)
|
||||
{
|
||||
currentState->transform = transform.followedBy (currentState->transform);
|
||||
}
|
||||
|
||||
float Direct2DLowLevelGraphicsContext::getPhysicalPixelScaleFactor()
|
||||
{
|
||||
return currentState->transform.getScaleFactor();
|
||||
}
|
||||
|
||||
bool Direct2DLowLevelGraphicsContext::clipToRectangle (const Rectangle<int>& r)
|
||||
{
|
||||
currentState->clipToRectangle (r);
|
||||
return ! isClipEmpty();
|
||||
}
|
||||
|
||||
bool Direct2DLowLevelGraphicsContext::clipToRectangleList (const RectangleList<int>& clipRegion)
|
||||
{
|
||||
currentState->clipToRectList (pimpl->rectListToPathGeometry (clipRegion));
|
||||
return ! isClipEmpty();
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::excludeClipRectangle (const Rectangle<int>&)
|
||||
{
|
||||
//xxx
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform)
|
||||
{
|
||||
currentState->clipToPath (pimpl->pathToPathGeometry (path, transform));
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform)
|
||||
{
|
||||
currentState->clipToImage (sourceImage, transform);
|
||||
}
|
||||
|
||||
bool Direct2DLowLevelGraphicsContext::clipRegionIntersects (const Rectangle<int>& r)
|
||||
{
|
||||
return currentState->clipRect.intersects (r.toFloat().transformed (currentState->transform).getSmallestIntegerContainer());
|
||||
}
|
||||
|
||||
Rectangle<int> Direct2DLowLevelGraphicsContext::getClipBounds() const
|
||||
{
|
||||
// xxx could this take into account complex clip regions?
|
||||
return currentState->clipRect.toFloat().transformed (currentState->transform.inverted()).getSmallestIntegerContainer();
|
||||
}
|
||||
|
||||
bool Direct2DLowLevelGraphicsContext::isClipEmpty() const
|
||||
{
|
||||
return currentState->clipRect.isEmpty();
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::saveState()
|
||||
{
|
||||
states.add (new SavedState (*this));
|
||||
currentState = states.getLast();
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::restoreState()
|
||||
{
|
||||
jassert (states.size() > 1); //you should never pop the last state!
|
||||
states.removeLast (1);
|
||||
currentState = states.getLast();
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::beginTransparencyLayer (float /*opacity*/)
|
||||
{
|
||||
jassertfalse; //xxx todo
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::endTransparencyLayer()
|
||||
{
|
||||
jassertfalse; //xxx todo
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::setFill (const FillType& fillType)
|
||||
{
|
||||
currentState->setFill (fillType);
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::setOpacity (float newOpacity)
|
||||
{
|
||||
currentState->setOpacity (newOpacity);
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/)
|
||||
{
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::fillRect (const Rectangle<int>& r, bool /*replaceExistingContents*/)
|
||||
{
|
||||
fillRect (r.toFloat());
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::fillRect (const Rectangle<float>& r)
|
||||
{
|
||||
pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform));
|
||||
currentState->createBrush();
|
||||
pimpl->renderingTarget->FillRectangle (rectangleToRectF (r), currentState->currentBrush);
|
||||
pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::fillRectList (const RectangleList<float>& list)
|
||||
{
|
||||
for (auto& r : list)
|
||||
fillRect (r);
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::fillPath (const Path& p, const AffineTransform& transform)
|
||||
{
|
||||
currentState->createBrush();
|
||||
ComSmartPtr<ID2D1Geometry> geometry (pimpl->pathToPathGeometry (p, transform.followedBy (currentState->transform)));
|
||||
|
||||
if (pimpl->renderingTarget != nullptr)
|
||||
pimpl->renderingTarget->FillGeometry (geometry, currentState->currentBrush);
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::drawImage (const Image& image, const AffineTransform& transform)
|
||||
{
|
||||
pimpl->renderingTarget->SetTransform (transformToMatrix (transform.followedBy (currentState->transform)));
|
||||
|
||||
D2D1_SIZE_U size;
|
||||
size.width = image.getWidth();
|
||||
size.height = image.getHeight();
|
||||
|
||||
D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties();
|
||||
|
||||
Image img (image.convertedToFormat (Image::ARGB));
|
||||
Image::BitmapData bd (img, Image::BitmapData::readOnly);
|
||||
bp.pixelFormat = pimpl->renderingTarget->GetPixelFormat();
|
||||
bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
|
||||
|
||||
{
|
||||
ComSmartPtr<ID2D1Bitmap> tempBitmap;
|
||||
pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, tempBitmap.resetAndGetPointerAddress());
|
||||
if (tempBitmap != nullptr)
|
||||
pimpl->renderingTarget->DrawBitmap (tempBitmap);
|
||||
}
|
||||
|
||||
pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::drawLine (const Line <float>& line)
|
||||
{
|
||||
// xxx doesn't seem to be correctly aligned, may need nudging by 0.5 to match the software renderer's behaviour
|
||||
pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform));
|
||||
currentState->createBrush();
|
||||
|
||||
pimpl->renderingTarget->DrawLine (D2D1::Point2F (line.getStartX(), line.getStartY()),
|
||||
D2D1::Point2F (line.getEndX(), line.getEndY()),
|
||||
currentState->currentBrush);
|
||||
pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::setFont (const Font& newFont)
|
||||
{
|
||||
currentState->setFont (newFont);
|
||||
}
|
||||
|
||||
const Font& Direct2DLowLevelGraphicsContext::getFont()
|
||||
{
|
||||
return currentState->font;
|
||||
}
|
||||
|
||||
void Direct2DLowLevelGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& transform)
|
||||
{
|
||||
currentState->createBrush();
|
||||
currentState->createFont();
|
||||
|
||||
float hScale = currentState->font.getHorizontalScale();
|
||||
|
||||
pimpl->renderingTarget->SetTransform (transformToMatrix (AffineTransform::scale (hScale, 1.0f)
|
||||
.followedBy (transform)
|
||||
.followedBy (currentState->transform)));
|
||||
|
||||
const UINT16 glyphIndices = (UINT16) glyphNumber;
|
||||
const FLOAT glyphAdvances = 0;
|
||||
DWRITE_GLYPH_OFFSET offset;
|
||||
offset.advanceOffset = 0;
|
||||
offset.ascenderOffset = 0;
|
||||
|
||||
DWRITE_GLYPH_RUN glyphRun;
|
||||
glyphRun.fontFace = currentState->currentFontFace;
|
||||
glyphRun.fontEmSize = (FLOAT) (currentState->font.getHeight() * currentState->fontHeightToEmSizeFactor);
|
||||
glyphRun.glyphCount = 1;
|
||||
glyphRun.glyphIndices = &glyphIndices;
|
||||
glyphRun.glyphAdvances = &glyphAdvances;
|
||||
glyphRun.glyphOffsets = &offset;
|
||||
glyphRun.isSideways = FALSE;
|
||||
glyphRun.bidiLevel = 0;
|
||||
|
||||
pimpl->renderingTarget->DrawGlyphRun (D2D1::Point2F (0, 0), &glyphRun, currentState->currentBrush);
|
||||
pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
|
||||
}
|
||||
|
||||
bool Direct2DLowLevelGraphicsContext::drawTextLayout (const AttributedString& text, const Rectangle<float>& area)
|
||||
{
|
||||
pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform));
|
||||
|
||||
DirectWriteTypeLayout::drawToD2DContext (text, area,
|
||||
*(pimpl->renderingTarget),
|
||||
*(pimpl->factories->directWriteFactory),
|
||||
*(pimpl->factories->systemFonts));
|
||||
|
||||
pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#ifndef _WINDEF_
|
||||
class HWND__; // Forward or never
|
||||
typedef HWND__* HWND;
|
||||
#endif
|
||||
|
||||
class Direct2DLowLevelGraphicsContext : public LowLevelGraphicsContext
|
||||
{
|
||||
public:
|
||||
Direct2DLowLevelGraphicsContext (HWND);
|
||||
~Direct2DLowLevelGraphicsContext();
|
||||
|
||||
//==============================================================================
|
||||
bool isVectorDevice() const override { return false; }
|
||||
|
||||
void setOrigin (Point<int>) override;
|
||||
void addTransform (const AffineTransform&) override;
|
||||
float getPhysicalPixelScaleFactor() override;
|
||||
bool clipToRectangle (const Rectangle<int>&) override;
|
||||
bool clipToRectangleList (const RectangleList<int>&) override;
|
||||
void excludeClipRectangle (const Rectangle<int>&) override;
|
||||
void clipToPath (const Path&, const AffineTransform&) override;
|
||||
void clipToImageAlpha (const Image&, const AffineTransform&) override;
|
||||
bool clipRegionIntersects (const Rectangle<int>&) override;
|
||||
Rectangle<int> getClipBounds() const override;
|
||||
bool isClipEmpty() const override;
|
||||
|
||||
//==============================================================================
|
||||
void saveState() override;
|
||||
void restoreState() override;
|
||||
void beginTransparencyLayer (float opacity) override;
|
||||
void endTransparencyLayer() override;
|
||||
|
||||
//==============================================================================
|
||||
void setFill (const FillType&) override;
|
||||
void setOpacity (float) override;
|
||||
void setInterpolationQuality (Graphics::ResamplingQuality) override;
|
||||
|
||||
//==============================================================================
|
||||
void fillRect (const Rectangle<int>&, bool replaceExistingContents) override;
|
||||
void fillRect (const Rectangle<float>&) override;
|
||||
void fillRectList (const RectangleList<float>&) override;
|
||||
void fillPath (const Path&, const AffineTransform&) override;
|
||||
void drawImage (const Image& sourceImage, const AffineTransform&) override;
|
||||
|
||||
//==============================================================================
|
||||
void drawLine (const Line<float>&) override;
|
||||
void setFont (const Font&) override;
|
||||
const Font& getFont() override;
|
||||
void drawGlyph (int glyphNumber, const AffineTransform&) override;
|
||||
bool drawTextLayout (const AttributedString&, const Rectangle<float>&) override;
|
||||
|
||||
void resized();
|
||||
void clear();
|
||||
|
||||
void start();
|
||||
void end();
|
||||
|
||||
//==============================================================================
|
||||
private:
|
||||
struct SavedState;
|
||||
|
||||
HWND hwnd;
|
||||
|
||||
SavedState* currentState;
|
||||
OwnedArray<SavedState> states;
|
||||
|
||||
Rectangle<int> bounds;
|
||||
|
||||
struct Pimpl;
|
||||
friend struct Pimpl;
|
||||
friend struct ContainerDeletePolicy<Pimpl>;
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DLowLevelGraphicsContext)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,460 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
namespace DirectWriteTypeLayout
|
||||
{
|
||||
class CustomDirectWriteTextRenderer : public ComBaseClassHelper<IDWriteTextRenderer>
|
||||
{
|
||||
public:
|
||||
CustomDirectWriteTextRenderer (IDWriteFontCollection& fonts, const AttributedString& as)
|
||||
: ComBaseClassHelper<IDWriteTextRenderer> (0),
|
||||
attributedString (as),
|
||||
fontCollection (fonts),
|
||||
currentLine (-1),
|
||||
lastOriginY (-10000.0f)
|
||||
{
|
||||
}
|
||||
|
||||
JUCE_COMRESULT QueryInterface (REFIID refId, void** result) override
|
||||
{
|
||||
if (refId == __uuidof (IDWritePixelSnapping))
|
||||
return castToType<IDWritePixelSnapping> (result);
|
||||
|
||||
return ComBaseClassHelper<IDWriteTextRenderer>::QueryInterface (refId, result);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT IsPixelSnappingDisabled (void* /*clientDrawingContext*/, BOOL* isDisabled) override
|
||||
{
|
||||
*isDisabled = FALSE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetCurrentTransform (void*, DWRITE_MATRIX* matrix) override
|
||||
{
|
||||
matrix->m11 = 1.0f; matrix->m12 = 0.0f;
|
||||
matrix->m21 = 0.0f; matrix->m22 = 1.0f;
|
||||
matrix->dx = 0.0f; matrix->dy = 0.0f;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetPixelsPerDip (void*, FLOAT* pixelsPerDip) override
|
||||
{
|
||||
*pixelsPerDip = 1.0f;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT DrawUnderline (void*, FLOAT, FLOAT, DWRITE_UNDERLINE const*, IUnknown*) override
|
||||
{
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT DrawStrikethrough (void*, FLOAT, FLOAT, DWRITE_STRIKETHROUGH const*, IUnknown*) override
|
||||
{
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT DrawInlineObject (void*, FLOAT, FLOAT, IDWriteInlineObject*, BOOL, BOOL, IUnknown*) override
|
||||
{
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT DrawGlyphRun (void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE,
|
||||
DWRITE_GLYPH_RUN const* glyphRun, DWRITE_GLYPH_RUN_DESCRIPTION const* runDescription,
|
||||
IUnknown* clientDrawingEffect) override
|
||||
{
|
||||
TextLayout* const layout = static_cast<TextLayout*> (clientDrawingContext);
|
||||
|
||||
if (! (baselineOriginY >= -1.0e10f && baselineOriginY <= 1.0e10f))
|
||||
baselineOriginY = 0; // DirectWrite sometimes sends NaNs in this parameter
|
||||
|
||||
if (baselineOriginY != lastOriginY)
|
||||
{
|
||||
lastOriginY = baselineOriginY;
|
||||
++currentLine;
|
||||
|
||||
if (currentLine >= layout->getNumLines())
|
||||
{
|
||||
jassert (currentLine == layout->getNumLines());
|
||||
TextLayout::Line* const line = new TextLayout::Line();
|
||||
layout->addLine (line);
|
||||
|
||||
line->lineOrigin = Point<float> (baselineOriginX, baselineOriginY);
|
||||
}
|
||||
}
|
||||
|
||||
TextLayout::Line& glyphLine = layout->getLine (currentLine);
|
||||
|
||||
DWRITE_FONT_METRICS dwFontMetrics;
|
||||
glyphRun->fontFace->GetMetrics (&dwFontMetrics);
|
||||
|
||||
glyphLine.ascent = jmax (glyphLine.ascent, scaledFontSize (dwFontMetrics.ascent, dwFontMetrics, *glyphRun));
|
||||
glyphLine.descent = jmax (glyphLine.descent, scaledFontSize (dwFontMetrics.descent, dwFontMetrics, *glyphRun));
|
||||
|
||||
TextLayout::Run* const glyphRunLayout = new TextLayout::Run (Range<int> (runDescription->textPosition,
|
||||
runDescription->textPosition + runDescription->stringLength),
|
||||
glyphRun->glyphCount);
|
||||
glyphLine.runs.add (glyphRunLayout);
|
||||
|
||||
glyphRun->fontFace->GetMetrics (&dwFontMetrics);
|
||||
const float totalHeight = std::abs ((float) dwFontMetrics.ascent) + std::abs ((float) dwFontMetrics.descent);
|
||||
const float fontHeightToEmSizeFactor = (float) dwFontMetrics.designUnitsPerEm / totalHeight;
|
||||
|
||||
glyphRunLayout->font = getFontForRun (*glyphRun, glyphRun->fontEmSize / fontHeightToEmSizeFactor);
|
||||
glyphRunLayout->colour = getColourOf (static_cast<ID2D1SolidColorBrush*> (clientDrawingEffect));
|
||||
|
||||
const Point<float> lineOrigin (layout->getLine (currentLine).lineOrigin);
|
||||
float x = baselineOriginX - lineOrigin.x;
|
||||
|
||||
const float extraKerning = glyphRunLayout->font.getExtraKerningFactor()
|
||||
* glyphRunLayout->font.getHeight();
|
||||
|
||||
for (UINT32 i = 0; i < glyphRun->glyphCount; ++i)
|
||||
{
|
||||
const float advance = glyphRun->glyphAdvances[i];
|
||||
|
||||
if ((glyphRun->bidiLevel & 1) != 0)
|
||||
x -= advance + extraKerning; // RTL text
|
||||
|
||||
glyphRunLayout->glyphs.add (TextLayout::Glyph (glyphRun->glyphIndices[i],
|
||||
Point<float> (x, baselineOriginY - lineOrigin.y),
|
||||
advance));
|
||||
|
||||
if ((glyphRun->bidiLevel & 1) == 0)
|
||||
x += advance + extraKerning; // LTR text
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
const AttributedString& attributedString;
|
||||
IDWriteFontCollection& fontCollection;
|
||||
int currentLine;
|
||||
float lastOriginY;
|
||||
|
||||
static float scaledFontSize (int n, const DWRITE_FONT_METRICS& metrics, const DWRITE_GLYPH_RUN& glyphRun) noexcept
|
||||
{
|
||||
return (std::abs ((float) n) / (float) metrics.designUnitsPerEm) * glyphRun.fontEmSize;
|
||||
}
|
||||
|
||||
static Colour getColourOf (ID2D1SolidColorBrush* d2dBrush) noexcept
|
||||
{
|
||||
if (d2dBrush == nullptr)
|
||||
return Colours::black;
|
||||
|
||||
const D2D1_COLOR_F colour (d2dBrush->GetColor());
|
||||
return Colour::fromFloatRGBA (colour.r, colour.g, colour.b, colour.a);
|
||||
}
|
||||
|
||||
Font getFontForRun (const DWRITE_GLYPH_RUN& glyphRun, float fontHeight)
|
||||
{
|
||||
for (int i = 0; i < attributedString.getNumAttributes(); ++i)
|
||||
{
|
||||
const Font& font = attributedString.getAttribute(i).font;
|
||||
|
||||
if (WindowsDirectWriteTypeface* wt = dynamic_cast<WindowsDirectWriteTypeface*> (font.getTypeface()))
|
||||
if (wt->getIDWriteFontFace() == glyphRun.fontFace)
|
||||
return font.withHeight (fontHeight);
|
||||
}
|
||||
|
||||
ComSmartPtr<IDWriteFont> dwFont;
|
||||
HRESULT hr = fontCollection.GetFontFromFontFace (glyphRun.fontFace, dwFont.resetAndGetPointerAddress());
|
||||
jassert (dwFont != nullptr);
|
||||
|
||||
ComSmartPtr<IDWriteFontFamily> dwFontFamily;
|
||||
hr = dwFont->GetFontFamily (dwFontFamily.resetAndGetPointerAddress());
|
||||
|
||||
return Font (getFontFamilyName (dwFontFamily), getFontFaceName (dwFont), fontHeight);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomDirectWriteTextRenderer)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static float getFontHeightToEmSizeFactor (IDWriteFont& dwFont)
|
||||
{
|
||||
ComSmartPtr<IDWriteFontFace> dwFontFace;
|
||||
dwFont.CreateFontFace (dwFontFace.resetAndGetPointerAddress());
|
||||
|
||||
if (dwFontFace == nullptr)
|
||||
return 1.0f;
|
||||
|
||||
DWRITE_FONT_METRICS dwFontMetrics;
|
||||
dwFontFace->GetMetrics (&dwFontMetrics);
|
||||
|
||||
const float totalHeight = (float) (std::abs (dwFontMetrics.ascent) + std::abs (dwFontMetrics.descent));
|
||||
return dwFontMetrics.designUnitsPerEm / totalHeight;
|
||||
}
|
||||
|
||||
void setTextFormatProperties (const AttributedString& text, IDWriteTextFormat& format)
|
||||
{
|
||||
DWRITE_TEXT_ALIGNMENT alignment = DWRITE_TEXT_ALIGNMENT_LEADING;
|
||||
DWRITE_WORD_WRAPPING wrapType = DWRITE_WORD_WRAPPING_WRAP;
|
||||
|
||||
switch (text.getJustification().getOnlyHorizontalFlags())
|
||||
{
|
||||
case Justification::left: break;
|
||||
case Justification::right: alignment = DWRITE_TEXT_ALIGNMENT_TRAILING; break;
|
||||
case Justification::horizontallyCentred: alignment = DWRITE_TEXT_ALIGNMENT_CENTER; break;
|
||||
case Justification::horizontallyJustified: break; // DirectWrite cannot justify text, default to left alignment
|
||||
default: jassertfalse; break; // Illegal justification flags
|
||||
}
|
||||
|
||||
switch (text.getWordWrap())
|
||||
{
|
||||
case AttributedString::none: wrapType = DWRITE_WORD_WRAPPING_NO_WRAP; break;
|
||||
case AttributedString::byWord: break;
|
||||
case AttributedString::byChar: break; // DirectWrite doesn't support wrapping by character, default to word-wrap
|
||||
default: jassertfalse; break; // Illegal flags!
|
||||
}
|
||||
|
||||
// DirectWrite does not automatically set reading direction
|
||||
// This must be set correctly and manually when using RTL Scripts (Hebrew, Arabic)
|
||||
if (text.getReadingDirection() == AttributedString::rightToLeft)
|
||||
{
|
||||
format.SetReadingDirection (DWRITE_READING_DIRECTION_RIGHT_TO_LEFT);
|
||||
|
||||
switch (text.getJustification().getOnlyHorizontalFlags())
|
||||
{
|
||||
case Justification::left: alignment = DWRITE_TEXT_ALIGNMENT_TRAILING; break;
|
||||
case Justification::right: alignment = DWRITE_TEXT_ALIGNMENT_LEADING; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
format.SetTextAlignment (alignment);
|
||||
format.SetWordWrapping (wrapType);
|
||||
}
|
||||
|
||||
void addAttributedRange (const AttributedString::Attribute& attr, IDWriteTextLayout& textLayout,
|
||||
const int textLen, ID2D1RenderTarget& renderTarget, IDWriteFontCollection& fontCollection)
|
||||
{
|
||||
DWRITE_TEXT_RANGE range;
|
||||
range.startPosition = attr.range.getStart();
|
||||
range.length = jmin (attr.range.getLength(), textLen - attr.range.getStart());
|
||||
|
||||
{
|
||||
const String familyName (FontStyleHelpers::getConcreteFamilyName (attr.font));
|
||||
|
||||
BOOL fontFound = false;
|
||||
uint32 fontIndex;
|
||||
fontCollection.FindFamilyName (familyName.toWideCharPointer(), &fontIndex, &fontFound);
|
||||
|
||||
if (! fontFound)
|
||||
fontIndex = 0;
|
||||
|
||||
ComSmartPtr<IDWriteFontFamily> fontFamily;
|
||||
HRESULT hr = fontCollection.GetFontFamily (fontIndex, fontFamily.resetAndGetPointerAddress());
|
||||
|
||||
ComSmartPtr<IDWriteFont> dwFont;
|
||||
uint32 fontFacesCount = 0;
|
||||
fontFacesCount = fontFamily->GetFontCount();
|
||||
|
||||
for (int i = fontFacesCount; --i >= 0;)
|
||||
{
|
||||
hr = fontFamily->GetFont (i, dwFont.resetAndGetPointerAddress());
|
||||
|
||||
if (attr.font.getTypefaceStyle() == getFontFaceName (dwFont))
|
||||
break;
|
||||
}
|
||||
|
||||
textLayout.SetFontFamilyName (familyName.toWideCharPointer(), range);
|
||||
textLayout.SetFontWeight (dwFont->GetWeight(), range);
|
||||
textLayout.SetFontStretch (dwFont->GetStretch(), range);
|
||||
textLayout.SetFontStyle (dwFont->GetStyle(), range);
|
||||
|
||||
const float fontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (*dwFont);
|
||||
textLayout.SetFontSize (attr.font.getHeight() * fontHeightToEmSizeFactor, range);
|
||||
}
|
||||
|
||||
{
|
||||
const Colour col (attr.colour);
|
||||
ComSmartPtr<ID2D1SolidColorBrush> d2dBrush;
|
||||
renderTarget.CreateSolidColorBrush (D2D1::ColorF (col.getFloatRed(),
|
||||
col.getFloatGreen(),
|
||||
col.getFloatBlue(),
|
||||
col.getFloatAlpha()),
|
||||
d2dBrush.resetAndGetPointerAddress());
|
||||
|
||||
// We need to call SetDrawingEffect with a legimate brush to get DirectWrite to break text based on colours
|
||||
textLayout.SetDrawingEffect (d2dBrush, range);
|
||||
}
|
||||
}
|
||||
|
||||
bool setupLayout (const AttributedString& text, const float maxWidth, const float maxHeight,
|
||||
ID2D1RenderTarget& renderTarget, IDWriteFactory& directWriteFactory,
|
||||
IDWriteFontCollection& fontCollection, ComSmartPtr<IDWriteTextLayout>& textLayout)
|
||||
{
|
||||
// To add color to text, we need to create a D2D render target
|
||||
// Since we are not actually rendering to a D2D context we create a temporary GDI render target
|
||||
|
||||
Font defaultFont;
|
||||
BOOL fontFound = false;
|
||||
uint32 fontIndex;
|
||||
fontCollection.FindFamilyName (defaultFont.getTypeface()->getName().toWideCharPointer(), &fontIndex, &fontFound);
|
||||
|
||||
if (! fontFound)
|
||||
fontIndex = 0;
|
||||
|
||||
ComSmartPtr<IDWriteFontFamily> dwFontFamily;
|
||||
HRESULT hr = fontCollection.GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress());
|
||||
|
||||
ComSmartPtr<IDWriteFont> dwFont;
|
||||
hr = dwFontFamily->GetFirstMatchingFont (DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL,
|
||||
dwFont.resetAndGetPointerAddress());
|
||||
jassert (dwFont != nullptr);
|
||||
|
||||
const float defaultFontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (*dwFont);
|
||||
|
||||
ComSmartPtr<IDWriteTextFormat> dwTextFormat;
|
||||
hr = directWriteFactory.CreateTextFormat (defaultFont.getTypefaceName().toWideCharPointer(), &fontCollection,
|
||||
DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
|
||||
defaultFont.getHeight() * defaultFontHeightToEmSizeFactor,
|
||||
L"en-us", dwTextFormat.resetAndGetPointerAddress());
|
||||
|
||||
setTextFormatProperties (text, *dwTextFormat);
|
||||
|
||||
{
|
||||
DWRITE_TRIMMING trimming = { DWRITE_TRIMMING_GRANULARITY_CHARACTER, 0, 0 };
|
||||
ComSmartPtr<IDWriteInlineObject> trimmingSign;
|
||||
hr = directWriteFactory.CreateEllipsisTrimmingSign (dwTextFormat, trimmingSign.resetAndGetPointerAddress());
|
||||
hr = dwTextFormat->SetTrimming (&trimming, trimmingSign);
|
||||
}
|
||||
|
||||
const int textLen = text.getText().length();
|
||||
|
||||
hr = directWriteFactory.CreateTextLayout (text.getText().toWideCharPointer(), textLen, dwTextFormat,
|
||||
maxWidth, maxHeight, textLayout.resetAndGetPointerAddress());
|
||||
|
||||
if (FAILED (hr) || textLayout == nullptr)
|
||||
return false;
|
||||
|
||||
const int numAttributes = text.getNumAttributes();
|
||||
|
||||
for (int i = 0; i < numAttributes; ++i)
|
||||
addAttributedRange (text.getAttribute (i), *textLayout, textLen, renderTarget, fontCollection);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void createLayout (TextLayout& layout, const AttributedString& text,
|
||||
IDWriteFactory& directWriteFactory,
|
||||
IDWriteFontCollection& fontCollection,
|
||||
ID2D1DCRenderTarget& renderTarget)
|
||||
{
|
||||
ComSmartPtr<IDWriteTextLayout> dwTextLayout;
|
||||
|
||||
if (! setupLayout (text, layout.getWidth(), layout.getHeight(), renderTarget,
|
||||
directWriteFactory, fontCollection, dwTextLayout))
|
||||
return;
|
||||
|
||||
UINT32 actualLineCount = 0;
|
||||
HRESULT hr = dwTextLayout->GetLineMetrics (nullptr, 0, &actualLineCount);
|
||||
|
||||
layout.ensureStorageAllocated (actualLineCount);
|
||||
|
||||
{
|
||||
ComSmartPtr<CustomDirectWriteTextRenderer> textRenderer (new CustomDirectWriteTextRenderer (fontCollection, text));
|
||||
hr = dwTextLayout->Draw (&layout, textRenderer, 0, 0);
|
||||
}
|
||||
|
||||
HeapBlock<DWRITE_LINE_METRICS> dwLineMetrics (actualLineCount);
|
||||
hr = dwTextLayout->GetLineMetrics (dwLineMetrics, actualLineCount, &actualLineCount);
|
||||
int lastLocation = 0;
|
||||
const int numLines = jmin ((int) actualLineCount, layout.getNumLines());
|
||||
float yAdjustment = 0;
|
||||
const float extraLineSpacing = text.getLineSpacing();
|
||||
|
||||
for (int i = 0; i < numLines; ++i)
|
||||
{
|
||||
TextLayout::Line& line = layout.getLine (i);
|
||||
line.stringRange = Range<int> (lastLocation, (int) lastLocation + dwLineMetrics[i].length);
|
||||
line.lineOrigin.y += yAdjustment;
|
||||
yAdjustment += extraLineSpacing;
|
||||
lastLocation += dwLineMetrics[i].length;
|
||||
}
|
||||
}
|
||||
|
||||
void drawToD2DContext (const AttributedString& text, const Rectangle<float>& area, ID2D1RenderTarget& renderTarget,
|
||||
IDWriteFactory& directWriteFactory, IDWriteFontCollection& fontCollection)
|
||||
{
|
||||
ComSmartPtr<IDWriteTextLayout> dwTextLayout;
|
||||
|
||||
if (setupLayout (text, area.getWidth(), area.getHeight(), renderTarget,
|
||||
directWriteFactory, fontCollection, dwTextLayout))
|
||||
{
|
||||
ComSmartPtr<ID2D1SolidColorBrush> d2dBrush;
|
||||
renderTarget.CreateSolidColorBrush (D2D1::ColorF (0.0f, 0.0f, 0.0f, 1.0f),
|
||||
d2dBrush.resetAndGetPointerAddress());
|
||||
|
||||
renderTarget.DrawTextLayout (D2D1::Point2F ((float) area.getX(), (float) area.getY()),
|
||||
dwTextLayout, d2dBrush, D2D1_DRAW_TEXT_OPTIONS_CLIP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool canAllTypefacesBeUsedInLayout (const AttributedString& text)
|
||||
{
|
||||
const int numCharacterAttributes = text.getNumAttributes();
|
||||
|
||||
for (int i = 0; i < numCharacterAttributes; ++i)
|
||||
if (dynamic_cast<WindowsDirectWriteTypeface*> (text.getAttribute(i).font.getTypeface()) == nullptr)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool TextLayout::createNativeLayout (const AttributedString& text)
|
||||
{
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
if (! canAllTypefacesBeUsedInLayout (text))
|
||||
return false;
|
||||
|
||||
SharedResourcePointer<Direct2DFactories> factories;
|
||||
|
||||
if (factories->d2dFactory != nullptr && factories->systemFonts != nullptr)
|
||||
{
|
||||
DirectWriteTypeLayout::createLayout (*this, text,
|
||||
*factories->directWriteFactory,
|
||||
*factories->systemFonts,
|
||||
*factories->directWriteRenderTarget);
|
||||
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
ignoreUnused (text);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
327
modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp
Normal file
327
modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp
Normal file
@ -0,0 +1,327 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
namespace
|
||||
{
|
||||
static String getLocalisedName (IDWriteLocalizedStrings* names)
|
||||
{
|
||||
jassert (names != nullptr);
|
||||
|
||||
uint32 index = 0;
|
||||
BOOL exists = false;
|
||||
HRESULT hr = names->FindLocaleName (L"en-us", &index, &exists);
|
||||
if (! exists)
|
||||
index = 0;
|
||||
|
||||
uint32 length = 0;
|
||||
hr = names->GetStringLength (index, &length);
|
||||
|
||||
HeapBlock<wchar_t> name (length + 1);
|
||||
hr = names->GetString (index, name, length + 1);
|
||||
|
||||
return static_cast<const wchar_t*> (name);
|
||||
}
|
||||
|
||||
static String getFontFamilyName (IDWriteFontFamily* family)
|
||||
{
|
||||
jassert (family != nullptr);
|
||||
ComSmartPtr<IDWriteLocalizedStrings> familyNames;
|
||||
HRESULT hr = family->GetFamilyNames (familyNames.resetAndGetPointerAddress());
|
||||
jassert (SUCCEEDED (hr)); ignoreUnused (hr);
|
||||
return getLocalisedName (familyNames);
|
||||
}
|
||||
|
||||
static String getFontFaceName (IDWriteFont* font)
|
||||
{
|
||||
jassert (font != nullptr);
|
||||
ComSmartPtr<IDWriteLocalizedStrings> faceNames;
|
||||
HRESULT hr = font->GetFaceNames (faceNames.resetAndGetPointerAddress());
|
||||
jassert (SUCCEEDED (hr)); ignoreUnused (hr);
|
||||
return getLocalisedName (faceNames);
|
||||
}
|
||||
|
||||
inline Point<float> convertPoint (D2D1_POINT_2F p) noexcept { return Point<float> ((float) p.x, (float) p.y); }
|
||||
}
|
||||
|
||||
class Direct2DFactories
|
||||
{
|
||||
public:
|
||||
Direct2DFactories()
|
||||
{
|
||||
if (direct2dDll.open ("d2d1.dll"))
|
||||
{
|
||||
JUCE_LOAD_WINAPI_FUNCTION (direct2dDll, D2D1CreateFactory, d2d1CreateFactory,
|
||||
HRESULT, (D2D1_FACTORY_TYPE, REFIID, D2D1_FACTORY_OPTIONS*, void**))
|
||||
|
||||
if (d2d1CreateFactory != nullptr)
|
||||
{
|
||||
D2D1_FACTORY_OPTIONS options;
|
||||
options.debugLevel = D2D1_DEBUG_LEVEL_NONE;
|
||||
|
||||
d2d1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof (ID2D1Factory), &options,
|
||||
(void**) d2dFactory.resetAndGetPointerAddress());
|
||||
}
|
||||
}
|
||||
|
||||
if (directWriteDll.open ("DWrite.dll"))
|
||||
{
|
||||
JUCE_LOAD_WINAPI_FUNCTION (directWriteDll, DWriteCreateFactory, dWriteCreateFactory,
|
||||
HRESULT, (DWRITE_FACTORY_TYPE, REFIID, IUnknown**))
|
||||
|
||||
if (dWriteCreateFactory != nullptr)
|
||||
{
|
||||
dWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, __uuidof (IDWriteFactory),
|
||||
(IUnknown**) directWriteFactory.resetAndGetPointerAddress());
|
||||
|
||||
if (directWriteFactory != nullptr)
|
||||
directWriteFactory->GetSystemFontCollection (systemFonts.resetAndGetPointerAddress());
|
||||
}
|
||||
|
||||
if (d2dFactory != nullptr)
|
||||
{
|
||||
D2D1_RENDER_TARGET_PROPERTIES d2dRTProp = D2D1::RenderTargetProperties (D2D1_RENDER_TARGET_TYPE_SOFTWARE,
|
||||
D2D1::PixelFormat (DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
D2D1_ALPHA_MODE_IGNORE),
|
||||
0, 0,
|
||||
D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE,
|
||||
D2D1_FEATURE_LEVEL_DEFAULT);
|
||||
|
||||
d2dFactory->CreateDCRenderTarget (&d2dRTProp, directWriteRenderTarget.resetAndGetPointerAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~Direct2DFactories()
|
||||
{
|
||||
d2dFactory = nullptr; // (need to make sure these are released before deleting the DynamicLibrary objects)
|
||||
directWriteFactory = nullptr;
|
||||
systemFonts = nullptr;
|
||||
directWriteRenderTarget = nullptr;
|
||||
}
|
||||
|
||||
ComSmartPtr<ID2D1Factory> d2dFactory;
|
||||
ComSmartPtr<IDWriteFactory> directWriteFactory;
|
||||
ComSmartPtr<IDWriteFontCollection> systemFonts;
|
||||
ComSmartPtr<ID2D1DCRenderTarget> directWriteRenderTarget;
|
||||
|
||||
private:
|
||||
DynamicLibrary direct2dDll, directWriteDll;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DFactories)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class WindowsDirectWriteTypeface : public Typeface
|
||||
{
|
||||
public:
|
||||
WindowsDirectWriteTypeface (const Font& font, IDWriteFontCollection* fontCollection)
|
||||
: Typeface (font.getTypefaceName(), font.getTypefaceStyle()),
|
||||
unitsToHeightScaleFactor (1.0f), heightToPointsFactor (1.0f), ascent (0.0f)
|
||||
{
|
||||
jassert (fontCollection != nullptr);
|
||||
|
||||
BOOL fontFound = false;
|
||||
uint32 fontIndex = 0;
|
||||
HRESULT hr = fontCollection->FindFamilyName (font.getTypefaceName().toWideCharPointer(), &fontIndex, &fontFound);
|
||||
if (! fontFound)
|
||||
fontIndex = 0;
|
||||
|
||||
// Get the font family using the search results
|
||||
// Fonts like: Times New Roman, Times New Roman Bold, Times New Roman Italic are all in the same font family
|
||||
ComSmartPtr<IDWriteFontFamily> dwFontFamily;
|
||||
hr = fontCollection->GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress());
|
||||
|
||||
// Get a specific font in the font family using typeface style
|
||||
{
|
||||
ComSmartPtr<IDWriteFont> dwFont;
|
||||
|
||||
for (int i = (int) dwFontFamily->GetFontCount(); --i >= 0;)
|
||||
{
|
||||
hr = dwFontFamily->GetFont (i, dwFont.resetAndGetPointerAddress());
|
||||
|
||||
if (i == 0)
|
||||
break;
|
||||
|
||||
ComSmartPtr<IDWriteLocalizedStrings> faceNames;
|
||||
hr = dwFont->GetFaceNames (faceNames.resetAndGetPointerAddress());
|
||||
|
||||
if (font.getTypefaceStyle() == getLocalisedName (faceNames))
|
||||
break;
|
||||
}
|
||||
|
||||
jassert (dwFont != nullptr);
|
||||
hr = dwFont->CreateFontFace (dwFontFace.resetAndGetPointerAddress());
|
||||
}
|
||||
|
||||
if (dwFontFace != nullptr)
|
||||
{
|
||||
DWRITE_FONT_METRICS dwFontMetrics;
|
||||
dwFontFace->GetMetrics (&dwFontMetrics);
|
||||
|
||||
// All Font Metrics are in design units so we need to get designUnitsPerEm value
|
||||
// to get the metrics into Em/Design Independent Pixels
|
||||
designUnitsPerEm = dwFontMetrics.designUnitsPerEm;
|
||||
|
||||
ascent = std::abs ((float) dwFontMetrics.ascent);
|
||||
const float totalSize = ascent + std::abs ((float) dwFontMetrics.descent);
|
||||
ascent /= totalSize;
|
||||
unitsToHeightScaleFactor = designUnitsPerEm / totalSize;
|
||||
|
||||
HDC tempDC = GetDC (0);
|
||||
float dpi = (GetDeviceCaps (tempDC, LOGPIXELSX) + GetDeviceCaps (tempDC, LOGPIXELSY)) / 2.0f;
|
||||
heightToPointsFactor = (dpi / GetDeviceCaps (tempDC, LOGPIXELSY)) * unitsToHeightScaleFactor;
|
||||
ReleaseDC (0, tempDC);
|
||||
|
||||
const float pathAscent = (1024.0f * dwFontMetrics.ascent) / designUnitsPerEm;
|
||||
const float pathDescent = (1024.0f * dwFontMetrics.descent) / designUnitsPerEm;
|
||||
const float pathScale = 1.0f / (std::abs (pathAscent) + std::abs (pathDescent));
|
||||
pathTransform = AffineTransform::scale (pathScale);
|
||||
}
|
||||
}
|
||||
|
||||
bool loadedOk() const noexcept { return dwFontFace != nullptr; }
|
||||
|
||||
float getAscent() const { return ascent; }
|
||||
float getDescent() const { return 1.0f - ascent; }
|
||||
float getHeightToPointsFactor() const { return heightToPointsFactor; }
|
||||
|
||||
float getStringWidth (const String& text)
|
||||
{
|
||||
const CharPointer_UTF32 textUTF32 (text.toUTF32());
|
||||
const size_t len = textUTF32.length();
|
||||
|
||||
HeapBlock<UINT16> glyphIndices (len);
|
||||
dwFontFace->GetGlyphIndices (textUTF32, (UINT32) len, glyphIndices);
|
||||
|
||||
HeapBlock<DWRITE_GLYPH_METRICS> dwGlyphMetrics (len);
|
||||
dwFontFace->GetDesignGlyphMetrics (glyphIndices, (UINT32) len, dwGlyphMetrics, false);
|
||||
|
||||
float x = 0;
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
x += (float) dwGlyphMetrics[i].advanceWidth / designUnitsPerEm;
|
||||
|
||||
return x * unitsToHeightScaleFactor;
|
||||
}
|
||||
|
||||
void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets)
|
||||
{
|
||||
xOffsets.add (0);
|
||||
|
||||
const CharPointer_UTF32 textUTF32 (text.toUTF32());
|
||||
const size_t len = textUTF32.length();
|
||||
|
||||
HeapBlock<UINT16> glyphIndices (len);
|
||||
dwFontFace->GetGlyphIndices (textUTF32, (UINT32) len, glyphIndices);
|
||||
HeapBlock<DWRITE_GLYPH_METRICS> dwGlyphMetrics (len);
|
||||
dwFontFace->GetDesignGlyphMetrics (glyphIndices, (UINT32) len, dwGlyphMetrics, false);
|
||||
|
||||
float x = 0;
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
{
|
||||
x += (float) dwGlyphMetrics[i].advanceWidth / designUnitsPerEm;
|
||||
xOffsets.add (x * unitsToHeightScaleFactor);
|
||||
resultGlyphs.add (glyphIndices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool getOutlineForGlyph (int glyphNumber, Path& path)
|
||||
{
|
||||
jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty
|
||||
UINT16 glyphIndex = (UINT16) glyphNumber;
|
||||
ComSmartPtr<PathGeometrySink> pathGeometrySink (new PathGeometrySink());
|
||||
|
||||
dwFontFace->GetGlyphRunOutline (1024.0f, &glyphIndex, nullptr, nullptr, 1, false, false, pathGeometrySink);
|
||||
path = pathGeometrySink->path;
|
||||
|
||||
if (! pathTransform.isIdentity())
|
||||
path.applyTransform (pathTransform);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IDWriteFontFace* getIDWriteFontFace() const noexcept { return dwFontFace; }
|
||||
|
||||
float getUnitsToHeightScaleFactor() const noexcept { return unitsToHeightScaleFactor; }
|
||||
|
||||
private:
|
||||
SharedResourcePointer<Direct2DFactories> factories;
|
||||
ComSmartPtr<IDWriteFontFace> dwFontFace;
|
||||
float unitsToHeightScaleFactor, heightToPointsFactor, ascent;
|
||||
int designUnitsPerEm;
|
||||
AffineTransform pathTransform;
|
||||
|
||||
struct PathGeometrySink : public ComBaseClassHelper<IDWriteGeometrySink>
|
||||
{
|
||||
PathGeometrySink() : ComBaseClassHelper<IDWriteGeometrySink> (0) {}
|
||||
|
||||
void __stdcall AddBeziers (const D2D1_BEZIER_SEGMENT* beziers, UINT beziersCount) override
|
||||
{
|
||||
for (UINT i = 0; i < beziersCount; ++i)
|
||||
path.cubicTo (convertPoint (beziers[i].point1),
|
||||
convertPoint (beziers[i].point2),
|
||||
convertPoint (beziers[i].point3));
|
||||
}
|
||||
|
||||
void __stdcall AddLines (const D2D1_POINT_2F* points, UINT pointsCount) override
|
||||
{
|
||||
for (UINT i = 0; i < pointsCount; ++i)
|
||||
path.lineTo (convertPoint (points[i]));
|
||||
}
|
||||
|
||||
void __stdcall BeginFigure (D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN) override
|
||||
{
|
||||
path.startNewSubPath (convertPoint (startPoint));
|
||||
}
|
||||
|
||||
void __stdcall EndFigure (D2D1_FIGURE_END figureEnd) override
|
||||
{
|
||||
if (figureEnd == D2D1_FIGURE_END_CLOSED)
|
||||
path.closeSubPath();
|
||||
}
|
||||
|
||||
void __stdcall SetFillMode (D2D1_FILL_MODE fillMode) override
|
||||
{
|
||||
path.setUsingNonZeroWinding (fillMode == D2D1_FILL_MODE_WINDING);
|
||||
}
|
||||
|
||||
void __stdcall SetSegmentFlags (D2D1_PATH_SEGMENT) override {}
|
||||
JUCE_COMRESULT Close() override { return S_OK; }
|
||||
|
||||
Path path;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PathGeometrySink)
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsDirectWriteTypeface)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
649
modules/juce_graphics/native/juce_win32_Fonts.cpp
Normal file
649
modules/juce_graphics/native/juce_win32_Fonts.cpp
Normal file
@ -0,0 +1,649 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/* This is some quick-and-dirty code to extract the typeface name from a lump of TTF file data.
|
||||
It's needed because although win32 will happily load a TTF file from in-memory data, it won't
|
||||
tell you the name of the damned font that it just loaded.. and in order to actually use the font,
|
||||
you need to know its name!! Anyway, this awful hack seems to work for most fonts.
|
||||
*/
|
||||
namespace TTFNameExtractor
|
||||
{
|
||||
struct OffsetTable
|
||||
{
|
||||
uint32 version;
|
||||
uint16 numTables, searchRange, entrySelector, rangeShift;
|
||||
};
|
||||
|
||||
struct TableDirectory
|
||||
{
|
||||
char tag[4];
|
||||
uint32 checkSum, offset, length;
|
||||
};
|
||||
|
||||
struct NamingTable
|
||||
{
|
||||
uint16 formatSelector;
|
||||
uint16 numberOfNameRecords;
|
||||
uint16 offsetStartOfStringStorage;
|
||||
};
|
||||
|
||||
struct NameRecord
|
||||
{
|
||||
uint16 platformID, encodingID, languageID;
|
||||
uint16 nameID, stringLength, offsetFromStorageArea;
|
||||
};
|
||||
|
||||
static String parseNameRecord (MemoryInputStream& input, const NameRecord& nameRecord,
|
||||
const int64 directoryOffset, const int64 offsetOfStringStorage)
|
||||
{
|
||||
String result;
|
||||
const int64 oldPos = input.getPosition();
|
||||
input.setPosition (directoryOffset + offsetOfStringStorage + ByteOrder::swapIfLittleEndian (nameRecord.offsetFromStorageArea));
|
||||
const int stringLength = (int) ByteOrder::swapIfLittleEndian (nameRecord.stringLength);
|
||||
const int platformID = ByteOrder::swapIfLittleEndian (nameRecord.platformID);
|
||||
|
||||
if (platformID == 0 || platformID == 3)
|
||||
{
|
||||
const int numChars = stringLength / 2 + 1;
|
||||
HeapBlock<uint16> buffer;
|
||||
buffer.calloc (numChars + 1);
|
||||
input.read (buffer, stringLength);
|
||||
|
||||
for (int i = 0; i < numChars; ++i)
|
||||
buffer[i] = ByteOrder::swapIfLittleEndian (buffer[i]);
|
||||
|
||||
static_assert (sizeof (CharPointer_UTF16::CharType) == sizeof (uint16), "Sanity check UTF-16 type");
|
||||
result = CharPointer_UTF16 ((CharPointer_UTF16::CharType*) buffer.getData());
|
||||
}
|
||||
else
|
||||
{
|
||||
HeapBlock<char> buffer;
|
||||
buffer.calloc (stringLength + 1);
|
||||
input.read (buffer, stringLength);
|
||||
result = CharPointer_UTF8 (buffer.getData());
|
||||
}
|
||||
|
||||
input.setPosition (oldPos);
|
||||
return result;
|
||||
}
|
||||
|
||||
static String parseNameTable (MemoryInputStream& input, int64 directoryOffset)
|
||||
{
|
||||
input.setPosition (directoryOffset);
|
||||
|
||||
NamingTable namingTable = { 0 };
|
||||
input.read (&namingTable, sizeof (namingTable));
|
||||
|
||||
for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (namingTable.numberOfNameRecords); ++i)
|
||||
{
|
||||
NameRecord nameRecord = { 0 };
|
||||
input.read (&nameRecord, sizeof (nameRecord));
|
||||
|
||||
if (ByteOrder::swapIfLittleEndian (nameRecord.nameID) == 4)
|
||||
{
|
||||
const String result (parseNameRecord (input, nameRecord, directoryOffset,
|
||||
ByteOrder::swapIfLittleEndian (namingTable.offsetStartOfStringStorage)));
|
||||
|
||||
if (result.isNotEmpty())
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static String getTypefaceNameFromFile (MemoryInputStream& input)
|
||||
{
|
||||
OffsetTable offsetTable = { 0 };
|
||||
input.read (&offsetTable, sizeof (offsetTable));
|
||||
|
||||
for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (offsetTable.numTables); ++i)
|
||||
{
|
||||
TableDirectory tableDirectory;
|
||||
zerostruct (tableDirectory);
|
||||
input.read (&tableDirectory, sizeof (tableDirectory));
|
||||
|
||||
if (String (tableDirectory.tag, sizeof (tableDirectory.tag)).equalsIgnoreCase ("name"))
|
||||
return parseNameTable (input, ByteOrder::swapIfLittleEndian (tableDirectory.offset));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
namespace FontEnumerators
|
||||
{
|
||||
static int CALLBACK fontEnum2 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam)
|
||||
{
|
||||
if (lpelfe != nullptr && (type & RASTER_FONTTYPE) == 0)
|
||||
{
|
||||
const String fontName (lpelfe->elfLogFont.lfFaceName);
|
||||
((StringArray*) lParam)->addIfNotAlreadyThere (fontName.removeCharacters ("@"));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int CALLBACK fontEnum1 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam)
|
||||
{
|
||||
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));
|
||||
|
||||
HDC dc = CreateCompatibleDC (0);
|
||||
EnumFontFamiliesEx (dc, &lf,
|
||||
(FONTENUMPROCW) &fontEnum2,
|
||||
lParam, 0);
|
||||
DeleteDC (dc);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
StringArray Font::findAllTypefaceNames()
|
||||
{
|
||||
StringArray results;
|
||||
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
SharedResourcePointer<Direct2DFactories> factories;
|
||||
|
||||
if (factories->systemFonts != nullptr)
|
||||
{
|
||||
ComSmartPtr<IDWriteFontFamily> fontFamily;
|
||||
uint32 fontFamilyCount = 0;
|
||||
fontFamilyCount = factories->systemFonts->GetFontFamilyCount();
|
||||
|
||||
for (uint32 i = 0; i < fontFamilyCount; ++i)
|
||||
{
|
||||
HRESULT hr = factories->systemFonts->GetFontFamily (i, fontFamily.resetAndGetPointerAddress());
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
results.addIfNotAlreadyThere (getFontFamilyName (fontFamily));
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
HDC dc = CreateCompatibleDC (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;
|
||||
|
||||
EnumFontFamiliesEx (dc, &lf,
|
||||
(FONTENUMPROCW) &FontEnumerators::fontEnum1,
|
||||
(LPARAM) &results, 0);
|
||||
}
|
||||
|
||||
DeleteDC (dc);
|
||||
}
|
||||
|
||||
results.sort (true);
|
||||
return results;
|
||||
}
|
||||
|
||||
StringArray Font::findAllTypefaceStyles (const String& family)
|
||||
{
|
||||
if (FontStyleHelpers::isPlaceholderFamilyName (family))
|
||||
return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));
|
||||
|
||||
StringArray results;
|
||||
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
SharedResourcePointer<Direct2DFactories> factories;
|
||||
|
||||
if (factories->systemFonts != nullptr)
|
||||
{
|
||||
BOOL fontFound = false;
|
||||
uint32 fontIndex = 0;
|
||||
HRESULT hr = factories->systemFonts->FindFamilyName (family.toWideCharPointer(), &fontIndex, &fontFound);
|
||||
if (! fontFound)
|
||||
fontIndex = 0;
|
||||
|
||||
// Get the font family using the search results
|
||||
// Fonts like: Times New Roman, Times New Roman Bold, Times New Roman Italic are all in the same font family
|
||||
ComSmartPtr<IDWriteFontFamily> fontFamily;
|
||||
hr = factories->systemFonts->GetFontFamily (fontIndex, fontFamily.resetAndGetPointerAddress());
|
||||
|
||||
// Get the font faces
|
||||
ComSmartPtr<IDWriteFont> dwFont;
|
||||
uint32 fontFacesCount = 0;
|
||||
fontFacesCount = fontFamily->GetFontCount();
|
||||
|
||||
for (uint32 i = 0; i < fontFacesCount; ++i)
|
||||
{
|
||||
hr = fontFamily->GetFont (i, dwFont.resetAndGetPointerAddress());
|
||||
|
||||
// Ignore any algorithmically generated bold and oblique styles..
|
||||
if (dwFont->GetSimulations() == DWRITE_FONT_SIMULATIONS_NONE)
|
||||
results.addIfNotAlreadyThere (getFontFaceName (dwFont));
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
results.add ("Regular");
|
||||
results.add ("Italic");
|
||||
results.add ("Bold");
|
||||
results.add ("Bold Italic");
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
extern bool juce_isRunningInWine();
|
||||
|
||||
struct DefaultFontNames
|
||||
{
|
||||
DefaultFontNames()
|
||||
{
|
||||
if (juce_isRunningInWine())
|
||||
{
|
||||
// If we're running in Wine, then use fonts that might be available on Linux..
|
||||
defaultSans = "Bitstream Vera Sans";
|
||||
defaultSerif = "Bitstream Vera Serif";
|
||||
defaultFixed = "Bitstream Vera Sans Mono";
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultSans = "Verdana";
|
||||
defaultSerif = "Times New Roman";
|
||||
defaultFixed = "Lucida Console";
|
||||
defaultFallback = "Tahoma"; // (contains plenty of unicode characters)
|
||||
}
|
||||
}
|
||||
|
||||
String defaultSans, defaultSerif, defaultFixed, defaultFallback;
|
||||
};
|
||||
|
||||
Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
|
||||
{
|
||||
static DefaultFontNames defaultNames;
|
||||
|
||||
Font newFont (font);
|
||||
const String& faceName = font.getTypefaceName();
|
||||
|
||||
if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans);
|
||||
else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif);
|
||||
else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed);
|
||||
|
||||
if (font.getTypefaceStyle() == getDefaultStyle())
|
||||
newFont.setTypefaceStyle ("Regular");
|
||||
|
||||
return Typeface::createSystemTypefaceFor (newFont);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class WindowsTypeface : public Typeface
|
||||
{
|
||||
public:
|
||||
WindowsTypeface (const Font& font)
|
||||
: Typeface (font.getTypefaceName(), font.getTypefaceStyle()),
|
||||
fontH (0), previousFontH (0),
|
||||
dc (CreateCompatibleDC (0)), memoryFont (0),
|
||||
ascent (1.0f), heightToPointsFactor (1.0f),
|
||||
defaultGlyph (-1)
|
||||
{
|
||||
loadFont();
|
||||
}
|
||||
|
||||
WindowsTypeface (const void* data, size_t dataSize)
|
||||
: Typeface (String(), String()),
|
||||
fontH (0), previousFontH (0),
|
||||
dc (CreateCompatibleDC (0)), memoryFont (0),
|
||||
ascent (1.0f), heightToPointsFactor (1.0f),
|
||||
defaultGlyph (-1)
|
||||
{
|
||||
DWORD numInstalled = 0;
|
||||
memoryFont = AddFontMemResourceEx (const_cast<void*> (data), (DWORD) dataSize,
|
||||
nullptr, &numInstalled);
|
||||
|
||||
MemoryInputStream m (data, dataSize, false);
|
||||
name = TTFNameExtractor::getTypefaceNameFromFile (m);
|
||||
loadFont();
|
||||
}
|
||||
|
||||
~WindowsTypeface()
|
||||
{
|
||||
SelectObject (dc, previousFontH); // Replacing the previous font before deleting the DC avoids a warning in BoundsChecker
|
||||
DeleteDC (dc);
|
||||
|
||||
if (fontH != 0)
|
||||
DeleteObject (fontH);
|
||||
|
||||
if (memoryFont != 0)
|
||||
RemoveFontMemResourceEx (memoryFont);
|
||||
}
|
||||
|
||||
float getAscent() const { return ascent; }
|
||||
float getDescent() const { return 1.0f - ascent; }
|
||||
float getHeightToPointsFactor() const { return heightToPointsFactor; }
|
||||
|
||||
float getStringWidth (const String& text)
|
||||
{
|
||||
const CharPointer_UTF16 utf16 (text.toUTF16());
|
||||
const size_t numChars = utf16.length();
|
||||
HeapBlock<uint16> results (numChars);
|
||||
float x = 0;
|
||||
|
||||
if (GetGlyphIndices (dc, utf16, (int) numChars, reinterpret_cast<WORD*> (results.getData()),
|
||||
GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR)
|
||||
{
|
||||
for (size_t i = 0; i < numChars; ++i)
|
||||
x += getKerning (dc, results[i], (i + 1) < numChars ? results[i + 1] : -1);
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
void getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array <float>& xOffsets)
|
||||
{
|
||||
const CharPointer_UTF16 utf16 (text.toUTF16());
|
||||
const size_t numChars = utf16.length();
|
||||
HeapBlock<uint16> results (numChars);
|
||||
float x = 0;
|
||||
|
||||
if (GetGlyphIndices (dc, utf16, (int) numChars, reinterpret_cast<WORD*> (results.getData()),
|
||||
GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR)
|
||||
{
|
||||
resultGlyphs.ensureStorageAllocated ((int) numChars);
|
||||
xOffsets.ensureStorageAllocated ((int) numChars + 1);
|
||||
|
||||
for (size_t i = 0; i < numChars; ++i)
|
||||
{
|
||||
resultGlyphs.add (results[i]);
|
||||
xOffsets.add (x);
|
||||
x += getKerning (dc, results[i], (i + 1) < numChars ? results[i + 1] : -1);
|
||||
}
|
||||
}
|
||||
|
||||
xOffsets.add (x);
|
||||
}
|
||||
|
||||
bool getOutlineForGlyph (int glyphNumber, Path& glyphPath)
|
||||
{
|
||||
if (glyphNumber < 0)
|
||||
glyphNumber = defaultGlyph;
|
||||
|
||||
GLYPHMETRICS gm;
|
||||
// (although GetGlyphOutline returns a DWORD, it may be -1 on failure, so treat it as signed int..)
|
||||
const int bufSize = (int) GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX,
|
||||
&gm, 0, 0, &identityMatrix);
|
||||
|
||||
if (bufSize > 0)
|
||||
{
|
||||
HeapBlock<char> data (bufSize);
|
||||
GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm,
|
||||
bufSize, data, &identityMatrix);
|
||||
|
||||
const TTPOLYGONHEADER* pheader = reinterpret_cast<TTPOLYGONHEADER*> (data.getData());
|
||||
|
||||
const float scaleX = 1.0f / tm.tmHeight;
|
||||
const float scaleY = -scaleX;
|
||||
|
||||
while ((char*) pheader < data + bufSize)
|
||||
{
|
||||
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;
|
||||
|
||||
while ((const char*) curve < curveEnd)
|
||||
{
|
||||
if (curve->wType == TT_PRIM_LINE)
|
||||
{
|
||||
for (int i = 0; i < curve->cpfx; ++i)
|
||||
glyphPath.lineTo (scaleX * curve->apfx[i].x.value,
|
||||
scaleY * curve->apfx[i].y.value);
|
||||
}
|
||||
else if (curve->wType == TT_PRIM_QSPLINE)
|
||||
{
|
||||
for (int i = 0; i < curve->cpfx - 1; ++i)
|
||||
{
|
||||
const float x2 = scaleX * curve->apfx[i].x.value;
|
||||
const float y2 = scaleY * curve->apfx[i].y.value;
|
||||
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 + x3);
|
||||
y3 = 0.5f * (y2 + y3);
|
||||
}
|
||||
|
||||
glyphPath.quadraticTo (x2, y2, x3, y3);
|
||||
}
|
||||
}
|
||||
|
||||
curve = (const TTPOLYCURVE*) &(curve->apfx [curve->cpfx]);
|
||||
}
|
||||
|
||||
pheader = (const TTPOLYGONHEADER*) curve;
|
||||
|
||||
glyphPath.closeSubPath();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
static const MAT2 identityMatrix;
|
||||
HFONT fontH;
|
||||
HGDIOBJ previousFontH;
|
||||
HDC dc;
|
||||
TEXTMETRIC tm;
|
||||
HANDLE memoryFont;
|
||||
float ascent, heightToPointsFactor;
|
||||
int defaultGlyph, heightInPoints;
|
||||
|
||||
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) (style.contains ("Italic") ? TRUE : FALSE);
|
||||
lf.lfWeight = style.contains ("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)
|
||||
{
|
||||
heightInPoints = otm.otmEMSquare;
|
||||
lf.lfHeight = -(int) heightInPoints;
|
||||
fontH = CreateFontIndirect (&lf);
|
||||
|
||||
SelectObject (dc, fontH);
|
||||
DeleteObject (standardSizedFont);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GetTextMetrics (dc, &tm))
|
||||
{
|
||||
float dpi = (GetDeviceCaps (dc, LOGPIXELSX) + GetDeviceCaps (dc, LOGPIXELSY)) / 2.0f;
|
||||
heightToPointsFactor = (dpi / GetDeviceCaps (dc, LOGPIXELSY)) * heightInPoints / (float) tm.tmHeight;
|
||||
ascent = tm.tmAscent / (float) tm.tmHeight;
|
||||
defaultGlyph = getGlyphForChar (dc, tm.tmDefaultChar);
|
||||
createKerningPairs (dc, (float) tm.tmHeight);
|
||||
}
|
||||
}
|
||||
|
||||
void createKerningPairs (HDC hdc, const float height)
|
||||
{
|
||||
HeapBlock<KERNINGPAIR> rawKerning;
|
||||
const DWORD numKPs = GetKerningPairs (hdc, 0, 0);
|
||||
rawKerning.calloc (numKPs);
|
||||
GetKerningPairs (hdc, numKPs, rawKerning);
|
||||
|
||||
kerningPairs.ensureStorageAllocated ((int) numKPs);
|
||||
|
||||
for (DWORD i = 0; i < numKPs; ++i)
|
||||
{
|
||||
KerningPair kp;
|
||||
kp.glyph1 = getGlyphForChar (hdc, rawKerning[i].wFirst);
|
||||
kp.glyph2 = getGlyphForChar (hdc, rawKerning[i].wSecond);
|
||||
|
||||
const int standardWidth = getGlyphWidth (hdc, 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, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, 0, &identityMatrix);
|
||||
return gm.gmCellIncX;
|
||||
}
|
||||
|
||||
float getKerning (HDC hdc, 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 (hdc, 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 } };
|
||||
|
||||
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
|
||||
{
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
SharedResourcePointer<Direct2DFactories> factories;
|
||||
|
||||
if (factories->systemFonts != nullptr)
|
||||
{
|
||||
std::unique_ptr<WindowsDirectWriteTypeface> wtf (new WindowsDirectWriteTypeface (font, factories->systemFonts));
|
||||
|
||||
if (wtf->loadedOk())
|
||||
return wtf.release();
|
||||
}
|
||||
#endif
|
||||
|
||||
return new WindowsTypeface (font);
|
||||
}
|
||||
|
||||
Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize)
|
||||
{
|
||||
return new WindowsTypeface (data, dataSize);
|
||||
}
|
||||
|
||||
void Typeface::scanFolderForFonts (const File&)
|
||||
{
|
||||
jassertfalse; // not implemented on this platform
|
||||
}
|
||||
|
||||
} // namespace juce
|
30
modules/juce_graphics/native/juce_win32_IconHelpers.cpp
Normal file
30
modules/juce_graphics/native/juce_win32_IconHelpers.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
Image JUCE_API getIconFromApplication (const String&, int) { return {}; }
|
||||
}
|
Reference in New Issue
Block a user