git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
2741
deps/juce/modules/juce_graphics/native/juce_RenderingHelpers.h
vendored
Normal file
2741
deps/juce/modules/juce_graphics/native/juce_RenderingHelpers.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
549
deps/juce/modules/juce_graphics/native/juce_android_Fonts.cpp
vendored
Normal file
549
deps/juce/modules/juce_graphics/native/juce_android_Fonts.cpp
vendored
Normal file
@ -0,0 +1,549 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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, CALLBACK) \
|
||||
STATICMETHOD (create, "create", "(Ljava/lang/String;I)Landroid/graphics/Typeface;") \
|
||||
STATICMETHOD (createFromFile, "createFromFile", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \
|
||||
STATICMETHOD (createFromAsset, "createFromAsset", "(Landroid/content/res/AssetManager;Ljava/lang/String;)Landroid/graphics/Typeface;")
|
||||
|
||||
DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "()V") \
|
||||
METHOD (computeBounds, "computeBounds", "(Landroid/graphics/RectF;Z)V")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidPath, "android/graphics/Path")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "()V") \
|
||||
FIELD (left, "left", "F") \
|
||||
FIELD (right, "right", "F") \
|
||||
FIELD (top, "top", "F") \
|
||||
FIELD (bottom, "bottom", "F") \
|
||||
METHOD (roundOut, "roundOut", "(Landroid/graphics/Rect;)V")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidRectF, "android/graphics/RectF")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
STATICMETHOD (getInstance, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;") \
|
||||
METHOD (update, "update", "([B)V") \
|
||||
METHOD (digest, "digest", "()[B")
|
||||
DECLARE_JNI_CLASS (JavaMessageDigest, "java/security/MessageDigest")
|
||||
#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 (getTypefaceFromAsset (name));
|
||||
|
||||
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 (LocalRef<jobject>(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile,
|
||||
javaString (fontFile.getFullPathName()).get())));
|
||||
else
|
||||
typeface = GlobalRef (LocalRef<jobject>(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())
|
||||
{
|
||||
auto* env = getEnv();
|
||||
auto cacheFile = getCacheFileForData (data, size);
|
||||
|
||||
typeface = GlobalRef (LocalRef<jobject>(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile,
|
||||
javaString (cacheFile.getFullPathName()).get())));
|
||||
|
||||
initialise (env);
|
||||
}
|
||||
|
||||
void initialise (JNIEnv* const env)
|
||||
{
|
||||
rect = GlobalRef (LocalRef<jobject>(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()));
|
||||
|
||||
charArray = GlobalRef (LocalRef<jobject>((jobject) env->NewCharArray (2)));
|
||||
|
||||
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();
|
||||
auto numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());
|
||||
jfloatArray widths = env->NewFloatArray ((int) 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();
|
||||
auto numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());
|
||||
jfloatArray widths = env->NewFloatArray ((int) 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
|
||||
Rectangle<int> bounds;
|
||||
auto* env = getEnv();
|
||||
|
||||
{
|
||||
LocalRef<jobject> matrix (GraphicsHelpers::createMatrix (env, AffineTransform::scale (referenceFontToUnits).followedBy (t)));
|
||||
|
||||
jboolean isCopy;
|
||||
auto* buffer = env->GetCharArrayElements ((jcharArray) charArray.get(), &isCopy);
|
||||
|
||||
buffer[0] = ch1; buffer[1] = ch2;
|
||||
env->ReleaseCharArrayElements ((jcharArray) charArray.get(), buffer, 0);
|
||||
|
||||
LocalRef<jobject> path (env->NewObject (AndroidPath, AndroidPath.constructor));
|
||||
LocalRef<jobject> boundsF (env->NewObject (AndroidRectF, AndroidRectF.constructor));
|
||||
|
||||
|
||||
env->CallVoidMethod (paint.get(), AndroidPaint.getCharsPath, charArray.get(), 0, (ch2 != 0 ? 2 : 1), 0.0f, 0.0f, path.get());
|
||||
|
||||
env->CallVoidMethod (path.get(), AndroidPath.computeBounds, boundsF.get(), 1);
|
||||
|
||||
env->CallBooleanMethod (matrix.get(), AndroidMatrix.mapRect, boundsF.get());
|
||||
|
||||
env->CallVoidMethod (boundsF.get(), AndroidRectF.roundOut, rect.get());
|
||||
|
||||
bounds = Rectangle<int>::leftTopRightBottom (env->GetIntField (rect.get(), AndroidRect.left) - 1,
|
||||
env->GetIntField (rect.get(), AndroidRect.top),
|
||||
env->GetIntField (rect.get(), AndroidRect.right) + 1,
|
||||
env->GetIntField (rect.get(), AndroidRect.bottom));
|
||||
|
||||
auto w = bounds.getWidth();
|
||||
auto h = jmax (1, bounds.getHeight());
|
||||
|
||||
LocalRef<jobject> bitmapConfig (env->CallStaticObjectMethod (AndroidBitmapConfig, AndroidBitmapConfig.valueOf, javaString ("ARGB_8888").get()));
|
||||
LocalRef<jobject> bitmap (env->CallStaticObjectMethod (AndroidBitmap, AndroidBitmap.createBitmap, w, h, bitmapConfig.get()));
|
||||
LocalRef<jobject> canvas (env->NewObject (AndroidCanvas, AndroidCanvas.create, bitmap.get()));
|
||||
|
||||
env->CallBooleanMethod (matrix.get(), AndroidMatrix.postTranslate, (float) -bounds.getX(), (float) -bounds.getY());
|
||||
env->CallVoidMethod (canvas.get(), AndroidCanvas.setMatrix, matrix.get());
|
||||
env->CallVoidMethod (canvas.get(), AndroidCanvas.drawPath, path.get(), paint.get());
|
||||
|
||||
int requiredRenderArraySize = w * h;
|
||||
if (requiredRenderArraySize > lastCachedRenderArraySize)
|
||||
{
|
||||
cachedRenderArray = GlobalRef (LocalRef<jobject> ((jobject) env->NewIntArray (requiredRenderArraySize)));
|
||||
lastCachedRenderArraySize = requiredRenderArraySize;
|
||||
}
|
||||
|
||||
env->CallVoidMethod (bitmap.get(), AndroidBitmap.getPixels, cachedRenderArray.get(), 0, w, 0, 0, w, h);
|
||||
env->CallVoidMethod (bitmap.get(), AndroidBitmap.recycle);
|
||||
}
|
||||
|
||||
EdgeTable* et = nullptr;
|
||||
|
||||
if (! bounds.isEmpty())
|
||||
{
|
||||
et = new EdgeTable (bounds);
|
||||
|
||||
jint* const maskDataElements = env->GetIntArrayElements ((jintArray) cachedRenderArray.get(), nullptr);
|
||||
const jint* mask = maskDataElements;
|
||||
|
||||
for (int y = bounds.getY(); y < bounds.getBottom(); ++y)
|
||||
{
|
||||
#if JUCE_LITTLE_ENDIAN
|
||||
const uint8* const lineBytes = ((const uint8*) mask) + 3;
|
||||
#else
|
||||
const uint8* const lineBytes = (const uint8*) mask;
|
||||
#endif
|
||||
|
||||
et->clipLineToMask (bounds.getX(), y, lineBytes, 4, bounds.getWidth());
|
||||
mask += bounds.getWidth();
|
||||
}
|
||||
|
||||
env->ReleaseIntArrayElements ((jintArray) cachedRenderArray.get(), maskDataElements, 0);
|
||||
}
|
||||
|
||||
return et;
|
||||
}
|
||||
|
||||
GlobalRef typeface, paint, rect, charArray, cachedRenderArray;
|
||||
float ascent, descent, heightToPointsFactor;
|
||||
int lastCachedRenderArraySize = -1;
|
||||
|
||||
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& fontStyle)
|
||||
{
|
||||
String path ("/system/fonts/" + family);
|
||||
|
||||
if (fontStyle.isNotEmpty())
|
||||
path << '-' << fontStyle;
|
||||
|
||||
return File (path + ".ttf");
|
||||
}
|
||||
|
||||
static LocalRef<jobject> getTypefaceFromAsset (const String& typefaceName)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jobject> assetManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getAssets));
|
||||
|
||||
if (assetManager == nullptr)
|
||||
return LocalRef<jobject>();
|
||||
|
||||
auto assetTypeface = env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromAsset, assetManager.get(),
|
||||
javaString ("fonts/" + typefaceName).get());
|
||||
|
||||
// this may throw
|
||||
if (env->ExceptionCheck() != 0)
|
||||
{
|
||||
env->ExceptionClear();
|
||||
return LocalRef<jobject>();
|
||||
}
|
||||
|
||||
return LocalRef<jobject> (assetTypeface);
|
||||
}
|
||||
|
||||
static File getCacheDirectory()
|
||||
{
|
||||
static File result = []()
|
||||
{
|
||||
auto appContext = getAppContext();
|
||||
|
||||
if (appContext != nullptr)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jobject> cacheFile (env->CallObjectMethod (appContext.get(), AndroidContext.getCacheDir));
|
||||
LocalRef<jstring> jPath ((jstring) env->CallObjectMethod (cacheFile.get(), JavaFile.getAbsolutePath));
|
||||
|
||||
return File (juceString (env, jPath.get()));
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return File();
|
||||
} ();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static HashMap<String, File>& getInMemoryFontCache()
|
||||
{
|
||||
static HashMap<String, File> cache;
|
||||
return cache;
|
||||
}
|
||||
|
||||
static File getCacheFileForData (const void* data, size_t size)
|
||||
{
|
||||
static CriticalSection cs;
|
||||
JNIEnv* const env = getEnv();
|
||||
|
||||
String key;
|
||||
{
|
||||
LocalRef<jobject> digest (env->CallStaticObjectMethod (JavaMessageDigest, JavaMessageDigest.getInstance, javaString("MD5").get()));
|
||||
LocalRef<jbyteArray> bytes(env->NewByteArray ((int) size));
|
||||
|
||||
jboolean ignore;
|
||||
auto* jbytes = env->GetByteArrayElements(bytes.get(), &ignore);
|
||||
memcpy(jbytes, data, size);
|
||||
env->ReleaseByteArrayElements(bytes.get(), jbytes, 0);
|
||||
|
||||
env->CallVoidMethod(digest.get(), JavaMessageDigest.update, bytes.get());
|
||||
LocalRef<jbyteArray> result((jbyteArray) env->CallObjectMethod(digest.get(), JavaMessageDigest.digest));
|
||||
|
||||
auto* md5Bytes = env->GetByteArrayElements(result.get(), &ignore);
|
||||
key = String::toHexString(md5Bytes, env->GetArrayLength(result.get()), 0);
|
||||
env->ReleaseByteArrayElements(result.get(), md5Bytes, 0);
|
||||
}
|
||||
|
||||
ScopedLock lock (cs);
|
||||
auto& mapEntry = getInMemoryFontCache().getReference (key);
|
||||
|
||||
if (mapEntry == File())
|
||||
{
|
||||
mapEntry = getCacheDirectory().getChildFile ("bindata_" + key);
|
||||
mapEntry.replaceWithData (data, size);
|
||||
}
|
||||
|
||||
return mapEntry;
|
||||
}
|
||||
|
||||
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
|
66
deps/juce/modules/juce_graphics/native/juce_android_GraphicsContext.cpp
vendored
Normal file
66
deps/juce/modules/juce_graphics/native/juce_android_GraphicsContext.cpp
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
LocalRef<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 LocalRef<jobject>(getEnv()->NewObject (AndroidPaint, AndroidPaint.constructor, constructorFlags));
|
||||
}
|
||||
|
||||
const LocalRef<jobject> createMatrix (JNIEnv* env, const AffineTransform& t)
|
||||
{
|
||||
auto m = LocalRef<jobject>(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
|
29
deps/juce/modules/juce_graphics/native/juce_android_IconHelpers.cpp
vendored
Normal file
29
deps/juce/modules/juce_graphics/native/juce_android_IconHelpers.cpp
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 {}; }
|
||||
}
|
453
deps/juce/modules/juce_graphics/native/juce_freetype_Fonts.cpp
vendored
Normal file
453
deps/juce/modules/juce_graphics/native/juce_freetype_Fonts.cpp
vendored
Normal file
@ -0,0 +1,453 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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()
|
||||
{
|
||||
if (FT_Init_FreeType (&library) != 0)
|
||||
{
|
||||
library = {};
|
||||
DBG ("Failed to initialize FreeType");
|
||||
}
|
||||
}
|
||||
|
||||
~FTLibWrapper()
|
||||
{
|
||||
if (library != nullptr)
|
||||
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)
|
||||
: library (ftLib)
|
||||
{
|
||||
if (FT_New_Face (ftLib->library, file.getFullPathName().toUTF8(), faceIndex, &face) != 0)
|
||||
face = {};
|
||||
}
|
||||
|
||||
FTFaceWrapper (const FTLibWrapper::Ptr& ftLib, const void* data, size_t dataSize, int faceIndex)
|
||||
: 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 = {};
|
||||
}
|
||||
|
||||
~FTFaceWrapper()
|
||||
{
|
||||
if (face != nullptr)
|
||||
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, 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)
|
||||
{
|
||||
auto ftFace = matchTypeface (fontName, fontStyle);
|
||||
|
||||
if (ftFace == nullptr) ftFace = matchTypeface (fontName, "Regular");
|
||||
if (ftFace == nullptr) ftFace = matchTypeface (fontName, {});
|
||||
|
||||
if (ftFace != nullptr)
|
||||
return createFace (ftFace->file, ftFace->faceIndex);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
StringArray findAllFamilyNames() const
|
||||
{
|
||||
StringArray s;
|
||||
|
||||
for (auto* face : faces)
|
||||
s.addIfNotAlreadyThere (face->family);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static int indexOfRegularStyle (const StringArray& styles)
|
||||
{
|
||||
int i = styles.indexOf ("Regular", true);
|
||||
|
||||
if (i >= 0)
|
||||
return i;
|
||||
|
||||
for (i = 0; i < styles.size(); ++i)
|
||||
if (! (styles[i].containsIgnoreCase ("Bold") || styles[i].containsIgnoreCase ("Italic")))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
StringArray findAllTypefaceStyles (const String& family) const
|
||||
{
|
||||
StringArray s;
|
||||
|
||||
for (auto* face : faces)
|
||||
if (face->family == family)
|
||||
s.addIfNotAlreadyThere (face->style);
|
||||
|
||||
// try to get a regular style to be first in the list
|
||||
auto regular = indexOfRegularStyle (s);
|
||||
|
||||
if (regular > 0)
|
||||
s.strings.swap (0, regular);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void scanFontPaths (const StringArray& paths)
|
||||
{
|
||||
for (auto& path : paths)
|
||||
{
|
||||
for (const auto& iter : RangedDirectoryIterator (File::getCurrentWorkingDirectory().getChildFile (path), true))
|
||||
if (iter.getFile().hasFileExtension ("ttf;pfb;pcf;otf"))
|
||||
scanFont (iter.getFile());
|
||||
}
|
||||
}
|
||||
|
||||
void getMonospacedNames (StringArray& monoSpaced) const
|
||||
{
|
||||
for (auto* face : faces)
|
||||
if (face->isMonospaced)
|
||||
monoSpaced.addIfNotAlreadyThere (face->family);
|
||||
}
|
||||
|
||||
void getSerifNames (StringArray& serif) const
|
||||
{
|
||||
for (auto* face : faces)
|
||||
if (! (face->isSansSerif || face->isMonospaced))
|
||||
serif.addIfNotAlreadyThere (face->family);
|
||||
}
|
||||
|
||||
void getSansSerifNames (StringArray& sansSerif) const
|
||||
{
|
||||
for (auto* face : faces)
|
||||
if (face->isSansSerif)
|
||||
sansSerif.addIfNotAlreadyThere (face->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 != nullptr)
|
||||
{
|
||||
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 (auto* face : faces)
|
||||
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 (auto* name : sansNames)
|
||||
if (family.containsIgnoreCase (name))
|
||||
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)
|
||||
{
|
||||
auto face = faceWrapper->face;
|
||||
auto 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)
|
||||
{
|
||||
auto scale = 1.0f / (float) (face->ascender - face->descender);
|
||||
Path destShape;
|
||||
|
||||
if (getGlyphShape (destShape, face->glyph->outline, scale))
|
||||
{
|
||||
addGlyph (character, destShape, (float) 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, float scaleX)
|
||||
{
|
||||
auto scaleY = -scaleX;
|
||||
auto* contours = outline.contours;
|
||||
auto* tags = outline.tags;
|
||||
auto* 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)
|
||||
{
|
||||
auto x = scaleX * (float) points[p].x;
|
||||
auto y = scaleY * (float) points[p].y;
|
||||
|
||||
if (p == startPoint)
|
||||
{
|
||||
if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Conic)
|
||||
{
|
||||
auto x2 = scaleX * (float) points[endPoint].x;
|
||||
auto y2 = scaleY * (float) 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;
|
||||
auto x2 = scaleX * (float) points[nextIndex].x;
|
||||
auto y2 = scaleY * (float) 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;
|
||||
|
||||
auto x2 = scaleX * (float) points[next1].x;
|
||||
auto y2 = scaleY * (float) points[next1].y;
|
||||
auto x3 = scaleX * (float) points[next2].x;
|
||||
auto y3 = scaleY * (float) 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)
|
||||
{
|
||||
auto height = (float) (face->ascender - face->descender);
|
||||
|
||||
uint32 rightGlyphIndex;
|
||||
auto 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, (float) kerning.x / height);
|
||||
|
||||
rightCharCode = FT_Get_Next_Char (face, rightCharCode, &rightGlyphIndex);
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (FreeTypeTypeface)
|
||||
};
|
||||
|
||||
} // namespace juce
|
197
deps/juce/modules/juce_graphics/native/juce_linux_Fonts.cpp
vendored
Normal file
197
deps/juce/modules/juce_graphics/native/juce_linux_Fonts.cpp
vendored
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 std::unique_ptr<XmlElement> findFontsConfFile()
|
||||
{
|
||||
static const char* pathsToSearch[] = { "/etc/fonts/fonts.conf",
|
||||
"/usr/share/fonts/fonts.conf",
|
||||
"/usr/local/etc/fonts/fonts.conf" };
|
||||
|
||||
for (auto* path : pathsToSearch)
|
||||
if (auto xml = parseXML (File (path)))
|
||||
return xml;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
StringArray FTTypefaceList::getDefaultFontDirectories()
|
||||
{
|
||||
StringArray fontDirs;
|
||||
|
||||
fontDirs.addTokens (String (CharPointer_UTF8 (getenv ("JUCE_FONT_PATH"))), ";,", "");
|
||||
fontDirs.removeEmptyStrings (true);
|
||||
|
||||
if (fontDirs.isEmpty())
|
||||
{
|
||||
if (auto fontsInfo = findFontsConfFile())
|
||||
{
|
||||
for (auto* e : fontsInfo->getChildWithTagNameIterator ("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
|
29
deps/juce/modules/juce_graphics/native/juce_linux_IconHelpers.cpp
vendored
Normal file
29
deps/juce/modules/juce_graphics/native/juce_linux_IconHelpers.cpp
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 {}; }
|
||||
}
|
150
deps/juce/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h
vendored
Normal file
150
deps/juce/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 detail
|
||||
{
|
||||
struct ColorSpaceDelete
|
||||
{
|
||||
void operator() (CGColorSpaceRef ptr) const noexcept { CGColorSpaceRelease (ptr); }
|
||||
};
|
||||
|
||||
struct ContextDelete
|
||||
{
|
||||
void operator() (CGContextRef ptr) const noexcept { CGContextRelease (ptr); }
|
||||
};
|
||||
|
||||
struct DataProviderDelete
|
||||
{
|
||||
void operator() (CGDataProviderRef ptr) const noexcept { CGDataProviderRelease (ptr); }
|
||||
};
|
||||
|
||||
struct ImageDelete
|
||||
{
|
||||
void operator() (CGImageRef ptr) const noexcept { CGImageRelease (ptr); }
|
||||
};
|
||||
|
||||
struct GradientDelete
|
||||
{
|
||||
void operator() (CGGradientRef ptr) const noexcept { CGGradientRelease (ptr); }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
using ColorSpacePtr = std::unique_ptr<CGColorSpace, ColorSpaceDelete>;
|
||||
using ContextPtr = std::unique_ptr<CGContext, ContextDelete>;
|
||||
using DataProviderPtr = std::unique_ptr<CGDataProvider, DataProviderDelete>;
|
||||
using ImagePtr = std::unique_ptr<CGImage, ImageDelete>;
|
||||
using GradientPtr = std::unique_ptr<CGGradient, GradientDelete>;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class CoreGraphicsContext : public LowLevelGraphicsContext
|
||||
{
|
||||
public:
|
||||
CoreGraphicsContext (CGContextRef context, float flipHeight);
|
||||
~CoreGraphicsContext() override;
|
||||
|
||||
//==============================================================================
|
||||
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:
|
||||
//==============================================================================
|
||||
detail::ContextPtr context;
|
||||
const CGFloat flipHeight;
|
||||
detail::ColorSpacePtr rgbColourSpace, greyColourSpace;
|
||||
mutable Rectangle<int> lastClipRect;
|
||||
mutable bool lastClipRectIsValid = false;
|
||||
|
||||
struct SavedState
|
||||
{
|
||||
SavedState();
|
||||
SavedState (const SavedState&);
|
||||
~SavedState();
|
||||
|
||||
void setFill (const FillType&);
|
||||
|
||||
FillType fillType;
|
||||
Font font;
|
||||
CGFontRef fontRef = {};
|
||||
CGAffineTransform textMatrix = CGAffineTransformIdentity,
|
||||
inverseTextMatrix = CGAffineTransformIdentity;
|
||||
detail::GradientPtr 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
|
952
deps/juce/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm
vendored
Normal file
952
deps/juce/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm
vendored
Normal file
@ -0,0 +1,952 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 class has been renamed from CoreGraphicsImage to avoid a symbol
|
||||
// collision in Pro Tools 2019.12 and possibly 2020 depending on the Pro Tools
|
||||
// release schedule.
|
||||
class CoreGraphicsPixelData : public ImagePixelData
|
||||
{
|
||||
public:
|
||||
CoreGraphicsPixelData (const Image::PixelFormat format, int w, int h, bool clearImage)
|
||||
: ImagePixelData (format, w, h)
|
||||
{
|
||||
pixelStride = format == Image::RGB ? 3 : ((format == Image::ARGB) ? 4 : 1);
|
||||
lineStride = (pixelStride * jmax (1, width) + 3) & ~3;
|
||||
|
||||
auto numComponents = (size_t) lineStride * (size_t) jmax (1, height);
|
||||
|
||||
# if JUCE_MAC && defined (MAC_OS_X_VERSION_10_14) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14
|
||||
// This version of the SDK intermittently requires a bit of extra space
|
||||
// at the end of the image data. This feels like something has gone
|
||||
// wrong in Apple's code.
|
||||
numComponents += (size_t) lineStride;
|
||||
#endif
|
||||
|
||||
imageDataHolder->data.allocate (numComponents, clearImage);
|
||||
|
||||
auto colourSpace = detail::ColorSpacePtr { CGColorSpaceCreateWithName ((format == Image::SingleChannel) ? kCGColorSpaceGenericGrayGamma2_2
|
||||
: kCGColorSpaceSRGB) };
|
||||
|
||||
context = detail::ContextPtr { CGBitmapContextCreate (imageDataHolder->data, (size_t) width, (size_t) height, 8, (size_t) lineStride,
|
||||
colourSpace.get(), getCGImageFlags (format)) };
|
||||
}
|
||||
|
||||
~CoreGraphicsPixelData() override
|
||||
{
|
||||
freeCachedImageRef();
|
||||
}
|
||||
|
||||
std::unique_ptr<LowLevelGraphicsContext> createLowLevelContext() override
|
||||
{
|
||||
freeCachedImageRef();
|
||||
sendDataChangeMessage();
|
||||
return std::make_unique<CoreGraphicsContext> (context.get(), height);
|
||||
}
|
||||
|
||||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override
|
||||
{
|
||||
bitmap.data = imageDataHolder->data + x * pixelStride + y * lineStride;
|
||||
bitmap.pixelFormat = pixelFormat;
|
||||
bitmap.lineStride = lineStride;
|
||||
bitmap.pixelStride = pixelStride;
|
||||
|
||||
if (mode != Image::BitmapData::readOnly)
|
||||
{
|
||||
freeCachedImageRef();
|
||||
sendDataChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
ImagePixelData::Ptr clone() override
|
||||
{
|
||||
auto im = new CoreGraphicsPixelData (pixelFormat, width, height, false);
|
||||
memcpy (im->imageDataHolder->data, imageDataHolder->data, (size_t) (lineStride * height));
|
||||
return *im;
|
||||
}
|
||||
|
||||
std::unique_ptr<ImageType> createType() const override { return std::make_unique<NativeImageType>(); }
|
||||
|
||||
//==============================================================================
|
||||
static CGImageRef getCachedImageRef (const Image& juceImage, CGColorSpaceRef colourSpace)
|
||||
{
|
||||
auto cgim = dynamic_cast<CoreGraphicsPixelData*> (juceImage.getPixelData());
|
||||
|
||||
if (cgim != nullptr && cgim->cachedImageRef != nullptr)
|
||||
{
|
||||
CGImageRetain (cgim->cachedImageRef.get());
|
||||
return cgim->cachedImageRef.get();
|
||||
}
|
||||
|
||||
CGImageRef ref = createImage (juceImage, colourSpace, false);
|
||||
|
||||
if (cgim != nullptr)
|
||||
cgim->cachedImageRef.reset (CGImageRetain (ref));
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
static CGImageRef createImage (const Image& juceImage, CGColorSpaceRef colourSpace, bool mustOutliveSource)
|
||||
{
|
||||
const Image::BitmapData srcData (juceImage, Image::BitmapData::readOnly);
|
||||
detail::DataProviderPtr provider;
|
||||
|
||||
if (mustOutliveSource)
|
||||
{
|
||||
CFUniquePtr<CFDataRef> data (CFDataCreate (nullptr,
|
||||
(const UInt8*) srcData.data,
|
||||
(CFIndex) ((size_t) srcData.lineStride * (size_t) srcData.height)));
|
||||
provider = detail::DataProviderPtr { CGDataProviderCreateWithCFData (data.get()) };
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* imageDataContainer = [] (const Image& img) -> HeapBlockContainer::Ptr*
|
||||
{
|
||||
if (auto* cgim = dynamic_cast<CoreGraphicsPixelData*> (img.getPixelData()))
|
||||
return new HeapBlockContainer::Ptr (cgim->imageDataHolder);
|
||||
|
||||
return nullptr;
|
||||
} (juceImage);
|
||||
|
||||
provider = detail::DataProviderPtr { CGDataProviderCreateWithData (imageDataContainer,
|
||||
srcData.data,
|
||||
(size_t) srcData.lineStride * (size_t) srcData.height,
|
||||
[] (void * __nullable info, const void*, size_t) { delete (HeapBlockContainer::Ptr*) info; }) };
|
||||
}
|
||||
|
||||
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.get(),
|
||||
nullptr, true, kCGRenderingIntentDefault);
|
||||
|
||||
return imageRef;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
detail::ContextPtr context;
|
||||
detail::ImagePtr cachedImageRef;
|
||||
|
||||
struct HeapBlockContainer : public ReferenceCountedObject
|
||||
{
|
||||
using Ptr = ReferenceCountedObjectPtr<HeapBlockContainer>;
|
||||
HeapBlock<uint8> data;
|
||||
};
|
||||
|
||||
HeapBlockContainer::Ptr imageDataHolder = new HeapBlockContainer();
|
||||
int pixelStride, lineStride;
|
||||
|
||||
private:
|
||||
void freeCachedImageRef()
|
||||
{
|
||||
cachedImageRef.reset();
|
||||
}
|
||||
|
||||
static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
|
||||
{
|
||||
#if JUCE_BIG_ENDIAN
|
||||
return format == Image::ARGB ? ((uint32_t) kCGImageAlphaPremultipliedFirst | (uint32_t) kCGBitmapByteOrder32Big) : kCGBitmapByteOrderDefault;
|
||||
#else
|
||||
return format == Image::ARGB ? ((uint32_t) kCGImageAlphaPremultipliedFirst | (uint32_t) kCGBitmapByteOrder32Little) : kCGBitmapByteOrderDefault;
|
||||
#endif
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsPixelData)
|
||||
};
|
||||
|
||||
ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const
|
||||
{
|
||||
return *new CoreGraphicsPixelData (format == Image::RGB ? Image::ARGB : format, width, height, clearImage);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ScopedCGContextState
|
||||
{
|
||||
explicit ScopedCGContextState (CGContextRef c) : context (c) { CGContextSaveGState (context); }
|
||||
~ScopedCGContextState() { CGContextRestoreGState (context); }
|
||||
|
||||
CGContextRef context;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
CoreGraphicsContext::CoreGraphicsContext (CGContextRef c, float h)
|
||||
: context (c),
|
||||
flipHeight (h),
|
||||
state (new SavedState())
|
||||
{
|
||||
CGContextRetain (context.get());
|
||||
CGContextSaveGState (context.get());
|
||||
|
||||
#if JUCE_MAC
|
||||
bool enableFontSmoothing
|
||||
#if JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING
|
||||
= false;
|
||||
#else
|
||||
= true;
|
||||
#endif
|
||||
|
||||
CGContextSetShouldSmoothFonts (context.get(), enableFontSmoothing);
|
||||
CGContextSetAllowsFontSmoothing (context.get(), enableFontSmoothing);
|
||||
#endif
|
||||
|
||||
CGContextSetShouldAntialias (context.get(), true);
|
||||
CGContextSetBlendMode (context.get(), kCGBlendModeNormal);
|
||||
rgbColourSpace.reset (CGColorSpaceCreateWithName (kCGColorSpaceSRGB));
|
||||
greyColourSpace.reset (CGColorSpaceCreateWithName (kCGColorSpaceGenericGrayGamma2_2));
|
||||
setFont (Font());
|
||||
}
|
||||
|
||||
CoreGraphicsContext::~CoreGraphicsContext()
|
||||
{
|
||||
CGContextRestoreGState (context.get());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CoreGraphicsContext::setOrigin (Point<int> o)
|
||||
{
|
||||
CGContextTranslateCTM (context.get(), 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);
|
||||
}
|
||||
|
||||
float CoreGraphicsContext::getPhysicalPixelScaleFactor()
|
||||
{
|
||||
auto t = CGContextGetUserSpaceToDeviceSpaceTransform (context.get());
|
||||
auto determinant = (t.a * t.d) - (t.c * t.b);
|
||||
|
||||
return (float) std::sqrt (std::abs (determinant));
|
||||
}
|
||||
|
||||
bool CoreGraphicsContext::clipToRectangle (const Rectangle<int>& r)
|
||||
{
|
||||
CGContextClipToRect (context.get(), 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.get(), 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.get(), 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.get());
|
||||
else
|
||||
CGContextEOClip (context.get());
|
||||
|
||||
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);
|
||||
|
||||
auto image = detail::ImagePtr { CoreGraphicsPixelData::createImage (singleChannelImage, greyColourSpace.get(), true) };
|
||||
|
||||
flip();
|
||||
auto t = AffineTransform::verticalFlip (sourceImage.getHeight()).followedBy (transform);
|
||||
applyTransform (t);
|
||||
|
||||
auto r = convertToCGRect (sourceImage.getBounds());
|
||||
CGContextClipToMask (context.get(), r, image.get());
|
||||
|
||||
applyTransform (t.inverted());
|
||||
flip();
|
||||
|
||||
lastClipRectIsValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CoreGraphicsContext::clipRegionIntersects (const Rectangle<int>& r)
|
||||
{
|
||||
return getClipBounds().intersects (r);
|
||||
}
|
||||
|
||||
Rectangle<int> CoreGraphicsContext::getClipBounds() const
|
||||
{
|
||||
if (! lastClipRectIsValid)
|
||||
{
|
||||
auto bounds = CGRectIntegral (CGContextGetClipBoundingBox (context.get()));
|
||||
|
||||
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.get());
|
||||
stateStack.add (new SavedState (*state));
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::restoreState()
|
||||
{
|
||||
CGContextRestoreGState (context.get());
|
||||
|
||||
if (auto* top = stateStack.getLast())
|
||||
{
|
||||
state.reset (top);
|
||||
CGContextSetTextMatrix (context.get(), state->textMatrix);
|
||||
|
||||
stateStack.removeLast (1, false);
|
||||
lastClipRectIsValid = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse; // trying to pop with an empty stack!
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::beginTransparencyLayer (float opacity)
|
||||
{
|
||||
saveState();
|
||||
CGContextSetAlpha (context.get(), opacity);
|
||||
CGContextBeginTransparencyLayer (context.get(), nullptr);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::endTransparencyLayer()
|
||||
{
|
||||
CGContextEndTransparencyLayer (context.get());
|
||||
restoreState();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CoreGraphicsContext::setFill (const FillType& fillType)
|
||||
{
|
||||
state->setFill (fillType);
|
||||
|
||||
if (fillType.isColour())
|
||||
{
|
||||
CGContextSetRGBFillColor (context.get(), fillType.colour.getFloatRed(), fillType.colour.getFloatGreen(),
|
||||
fillType.colour.getFloatBlue(), fillType.colour.getFloatAlpha());
|
||||
CGContextSetAlpha (context.get(), 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.get(), kCGInterpolationNone); return;
|
||||
case Graphics::mediumResamplingQuality: CGContextSetInterpolationQuality (context.get(), kCGInterpolationMedium); return;
|
||||
case Graphics::highResamplingQuality: CGContextSetInterpolationQuality (context.get(), kCGInterpolationHigh); return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CoreGraphicsContext::fillRect (const Rectangle<int>& r, 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, bool replaceExistingContents)
|
||||
{
|
||||
if (replaceExistingContents)
|
||||
{
|
||||
CGContextSetBlendMode (context.get(), kCGBlendModeCopy);
|
||||
fillCGRect (cgRect, false);
|
||||
CGContextSetBlendMode (context.get(), kCGBlendModeNormal);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state->fillType.isColour())
|
||||
{
|
||||
CGContextFillRect (context.get(), cgRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScopedCGContextState scopedState (context.get());
|
||||
|
||||
CGContextClipToRect (context.get(), cgRect);
|
||||
|
||||
if (state->fillType.isGradient())
|
||||
drawGradient();
|
||||
else
|
||||
drawImage (state->fillType.image, state->fillType.transform, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::fillPath (const Path& path, const AffineTransform& transform)
|
||||
{
|
||||
ScopedCGContextState scopedState (context.get());
|
||||
|
||||
if (state->fillType.isColour())
|
||||
{
|
||||
flip();
|
||||
applyTransform (transform);
|
||||
createPath (path);
|
||||
|
||||
if (path.isUsingNonZeroWinding())
|
||||
CGContextFillPath (context.get());
|
||||
else
|
||||
CGContextEOFillPath (context.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
createPath (path, transform);
|
||||
|
||||
if (path.isUsingNonZeroWinding())
|
||||
CGContextClip (context.get());
|
||||
else
|
||||
CGContextEOClip (context.get());
|
||||
|
||||
if (state->fillType.isGradient())
|
||||
drawGradient();
|
||||
else
|
||||
drawImage (state->fillType.image, state->fillType.transform, true);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform)
|
||||
{
|
||||
drawImage (sourceImage, transform, false);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform, bool fillEntireClipAsTiles)
|
||||
{
|
||||
auto iw = sourceImage.getWidth();
|
||||
auto ih = sourceImage.getHeight();
|
||||
|
||||
auto colourSpace = sourceImage.getFormat() == Image::PixelFormat::SingleChannel ? greyColourSpace.get()
|
||||
: rgbColourSpace.get();
|
||||
auto image = detail::ImagePtr { CoreGraphicsPixelData::getCachedImageRef (sourceImage, colourSpace) };
|
||||
|
||||
ScopedCGContextState scopedState (context.get());
|
||||
CGContextSetAlpha (context.get(), state->fillType.getOpacity());
|
||||
|
||||
flip();
|
||||
applyTransform (AffineTransform::verticalFlip (ih).followedBy (transform));
|
||||
auto imageRect = CGRectMake (0, 0, iw, ih);
|
||||
|
||||
if (fillEntireClipAsTiles)
|
||||
{
|
||||
#if JUCE_IOS
|
||||
CGContextDrawTiledImage (context.get(), imageRect, image.get());
|
||||
#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 != nullptr && transform.isOnlyTranslation())
|
||||
{
|
||||
CGContextDrawTiledImage (context.get(), imageRect, image.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to manually doing a tiled fill
|
||||
auto clip = CGRectIntegral (CGContextGetClipBoundingBox (context.get()));
|
||||
|
||||
int x = 0, y = 0;
|
||||
while (x > clip.origin.x) x -= iw;
|
||||
while (y > clip.origin.y) y -= ih;
|
||||
|
||||
auto right = (int) (clip.origin.x + clip.size.width);
|
||||
auto bottom = (int) (clip.origin.y + clip.size.height);
|
||||
|
||||
while (y < bottom)
|
||||
{
|
||||
for (int x2 = x; x2 < right; x2 += iw)
|
||||
CGContextDrawImage (context.get(), CGRectMake (x2, y, iw, ih), image.get());
|
||||
|
||||
y += ih;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
CGContextDrawImage (context.get(), imageRect, image.get());
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CoreGraphicsContext::drawLine (const Line<float>& line)
|
||||
{
|
||||
Path p;
|
||||
p.addLineSegment (line, 1.0f);
|
||||
fillPath (p, {});
|
||||
}
|
||||
|
||||
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.get(), rects, num);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScopedCGContextState scopedState (context.get());
|
||||
|
||||
CGContextClipToRects (context.get(), rects, num);
|
||||
|
||||
if (state->fillType.isGradient())
|
||||
drawGradient();
|
||||
else
|
||||
drawImage (state->fillType.image, state->fillType.transform, true);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::setFont (const Font& newFont)
|
||||
{
|
||||
if (state->font != newFont)
|
||||
{
|
||||
state->fontRef = nullptr;
|
||||
state->font = newFont;
|
||||
|
||||
auto typeface = state->font.getTypefacePtr();
|
||||
|
||||
if (auto osxTypeface = dynamic_cast<OSXTypeface*> (typeface.get()))
|
||||
{
|
||||
state->fontRef = osxTypeface->fontRef;
|
||||
CGContextSetFont (context.get(), state->fontRef);
|
||||
CGContextSetFontSize (context.get(), state->font.getHeight() * osxTypeface->fontHeightToPointsFactor);
|
||||
|
||||
state->textMatrix = osxTypeface->renderingTransform;
|
||||
state->textMatrix.a *= state->font.getHorizontalScale();
|
||||
CGContextSetTextMatrix (context.get(), state->textMatrix);
|
||||
state->inverseTextMatrix = CGAffineTransformInvert (state->textMatrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Font& CoreGraphicsContext::getFont()
|
||||
{
|
||||
return state->font;
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& transform)
|
||||
{
|
||||
if (state->fontRef != nullptr && state->fillType.isColour())
|
||||
{
|
||||
auto cgTransformIsOnlyTranslation = [] (CGAffineTransform t)
|
||||
{
|
||||
return t.a == 1.0f && t.d == 1.0f && t.b == 0.0f && t.c == 0.0f;
|
||||
};
|
||||
|
||||
if (transform.isOnlyTranslation() && cgTransformIsOnlyTranslation (state->inverseTextMatrix))
|
||||
{
|
||||
auto x = transform.mat02 + state->inverseTextMatrix.tx;
|
||||
auto y = transform.mat12 + state->inverseTextMatrix.ty;
|
||||
|
||||
CGGlyph glyphs[1] = { (CGGlyph) glyphNumber };
|
||||
CGPoint positions[1] = { { x, flipHeight - roundToInt (y) } };
|
||||
CGContextShowGlyphsAtPositions (context.get(), glyphs, positions, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScopedCGContextState scopedState (context.get());
|
||||
|
||||
flip();
|
||||
applyTransform (transform);
|
||||
CGContextConcatCTM (context.get(), state->inverseTextMatrix);
|
||||
auto cgTransform = state->textMatrix;
|
||||
cgTransform.d = -cgTransform.d;
|
||||
CGContextConcatCTM (context.get(), cgTransform);
|
||||
|
||||
CGGlyph glyphs[1] = { (CGGlyph) glyphNumber };
|
||||
CGPoint positions[1] = { { 0.0f, 0.0f } };
|
||||
CGContextShowGlyphsAtPositions (context.get(), glyphs, positions, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Path p;
|
||||
auto& f = state->font;
|
||||
f.getTypefacePtr()->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)
|
||||
{
|
||||
return CoreTextTypeLayout::drawToCGContext (text, area, context.get(), (float) flipHeight);
|
||||
}
|
||||
|
||||
CoreGraphicsContext::SavedState::SavedState()
|
||||
: font (1.0f)
|
||||
{
|
||||
}
|
||||
|
||||
CoreGraphicsContext::SavedState::SavedState (const SavedState& other)
|
||||
: fillType (other.fillType), font (other.font), fontRef (other.fontRef),
|
||||
textMatrix (other.textMatrix), inverseTextMatrix (other.inverseTextMatrix),
|
||||
gradient (other.gradient.get())
|
||||
{
|
||||
if (gradient != nullptr)
|
||||
CGGradientRetain (gradient.get());
|
||||
}
|
||||
|
||||
CoreGraphicsContext::SavedState::~SavedState() = default;
|
||||
|
||||
void CoreGraphicsContext::SavedState::setFill (const FillType& newFill)
|
||||
{
|
||||
fillType = newFill;
|
||||
gradient = nullptr;
|
||||
}
|
||||
|
||||
static CGGradientRef createGradient (const ColourGradient& g, CGColorSpaceRef colourSpace)
|
||||
{
|
||||
auto numColours = g.getNumColours();
|
||||
auto data = (CGFloat*) alloca ((size_t) numColours * 5 * sizeof (CGFloat));
|
||||
auto locations = data;
|
||||
auto components = data + numColours;
|
||||
auto comps = components;
|
||||
|
||||
for (int i = 0; i < numColours; ++i)
|
||||
{
|
||||
auto 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.get(), state->fillType.getOpacity());
|
||||
|
||||
auto& g = *state->fillType.gradient;
|
||||
|
||||
if (state->gradient == nullptr)
|
||||
state->gradient.reset (createGradient (g, rgbColourSpace.get()));
|
||||
|
||||
auto p1 = convertToCGPoint (g.point1);
|
||||
auto p2 = convertToCGPoint (g.point2);
|
||||
|
||||
if (g.isRadial)
|
||||
CGContextDrawRadialGradient (context.get(), state->gradient.get(), p1, 0, p1, g.point1.getDistanceFrom (g.point2),
|
||||
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
||||
else
|
||||
CGContextDrawLinearGradient (context.get(), state->gradient.get(), p1, p2,
|
||||
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::createPath (const Path& path) const
|
||||
{
|
||||
CGContextBeginPath (context.get());
|
||||
|
||||
for (Path::Iterator i (path); i.next();)
|
||||
{
|
||||
switch (i.elementType)
|
||||
{
|
||||
case Path::Iterator::startNewSubPath: CGContextMoveToPoint (context.get(), i.x1, i.y1); break;
|
||||
case Path::Iterator::lineTo: CGContextAddLineToPoint (context.get(), i.x1, i.y1); break;
|
||||
case Path::Iterator::quadraticTo: CGContextAddQuadCurveToPoint (context.get(), i.x1, i.y1, i.x2, i.y2); break;
|
||||
case Path::Iterator::cubicTo: CGContextAddCurveToPoint (context.get(), i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); break;
|
||||
case Path::Iterator::closePath: CGContextClosePath (context.get()); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::createPath (const Path& path, const AffineTransform& transform) const
|
||||
{
|
||||
CGContextBeginPath (context.get());
|
||||
|
||||
for (Path::Iterator i (path); i.next();)
|
||||
{
|
||||
switch (i.elementType)
|
||||
{
|
||||
case Path::Iterator::startNewSubPath:
|
||||
transform.transformPoint (i.x1, i.y1);
|
||||
CGContextMoveToPoint (context.get(), i.x1, flipHeight - i.y1);
|
||||
break;
|
||||
case Path::Iterator::lineTo:
|
||||
transform.transformPoint (i.x1, i.y1);
|
||||
CGContextAddLineToPoint (context.get(), i.x1, flipHeight - i.y1);
|
||||
break;
|
||||
case Path::Iterator::quadraticTo:
|
||||
transform.transformPoints (i.x1, i.y1, i.x2, i.y2);
|
||||
CGContextAddQuadCurveToPoint (context.get(), 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.get(), i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2, i.x3, flipHeight - i.y3);
|
||||
break;
|
||||
case Path::Iterator::closePath:
|
||||
CGContextClosePath (context.get()); break;
|
||||
default:
|
||||
jassertfalse;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::flip() const
|
||||
{
|
||||
CGContextConcatCTM (context.get(), 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.get(), t);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if USE_COREGRAPHICS_RENDERING && JUCE_USE_COREIMAGE_LOADER
|
||||
Image juce_loadWithCoreImage (InputStream& input)
|
||||
{
|
||||
struct MemoryBlockHolder : public ReferenceCountedObject
|
||||
{
|
||||
using Ptr = ReferenceCountedObjectPtr<MemoryBlockHolder>;
|
||||
MemoryBlock block;
|
||||
};
|
||||
|
||||
MemoryBlockHolder::Ptr memBlockHolder = new MemoryBlockHolder();
|
||||
input.readIntoMemoryBlock (memBlockHolder->block, -1);
|
||||
|
||||
if (memBlockHolder->block.isEmpty())
|
||||
return {};
|
||||
|
||||
#if JUCE_IOS
|
||||
JUCE_AUTORELEASEPOOL
|
||||
#endif
|
||||
{
|
||||
#if JUCE_IOS
|
||||
if (UIImage* uiImage = [UIImage imageWithData: [NSData dataWithBytesNoCopy: memBlockHolder->block.getData()
|
||||
length: memBlockHolder->block.getSize()
|
||||
freeWhenDone: NO]])
|
||||
{
|
||||
CGImageRef loadedImage = uiImage.CGImage;
|
||||
|
||||
#else
|
||||
auto provider = detail::DataProviderPtr { CGDataProviderCreateWithData (new MemoryBlockHolder::Ptr (memBlockHolder),
|
||||
memBlockHolder->block.getData(),
|
||||
memBlockHolder->block.getSize(),
|
||||
[] (void * __nullable info, const void*, size_t) { delete (MemoryBlockHolder::Ptr*) info; }) };
|
||||
|
||||
if (auto imageSource = CFUniquePtr<CGImageSourceRef> (CGImageSourceCreateWithDataProvider (provider.get(), nullptr)))
|
||||
{
|
||||
CFUniquePtr<CGImageRef> loadedImagePtr (CGImageSourceCreateImageAtIndex (imageSource.get(), 0, nullptr));
|
||||
auto* loadedImage = loadedImagePtr.get();
|
||||
#endif
|
||||
|
||||
if (loadedImage != nullptr)
|
||||
{
|
||||
auto 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));
|
||||
|
||||
auto cgImage = dynamic_cast<CoreGraphicsPixelData*> (image.getPixelData());
|
||||
jassert (cgImage != nullptr); // if USE_COREGRAPHICS_RENDERING is set, the CoreGraphicsPixelData class should have been used.
|
||||
|
||||
CGContextDrawImage (cgImage->context.get(), convertToCGRect (image.getBounds()), loadedImage);
|
||||
CGContextFlush (cgImage->context.get());
|
||||
|
||||
// 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 {};
|
||||
}
|
||||
#endif
|
||||
|
||||
Image juce_createImageFromCIImage (CIImage*, int, int);
|
||||
Image juce_createImageFromCIImage (CIImage* im, int w, int h)
|
||||
{
|
||||
auto cgImage = new CoreGraphicsPixelData (Image::ARGB, w, h, false);
|
||||
|
||||
CIContext* cic = [CIContext contextWithCGContext: cgImage->context.get() options: nil];
|
||||
[cic drawImage: im inRect: CGRectMake (0, 0, w, h) fromRect: CGRectMake (0, 0, w, h)];
|
||||
CGContextFlush (cgImage->context.get());
|
||||
|
||||
return Image (*cgImage);
|
||||
}
|
||||
|
||||
CGImageRef juce_createCoreGraphicsImage (const Image& juceImage, CGColorSpaceRef colourSpace,
|
||||
const bool mustOutliveSource)
|
||||
{
|
||||
return CoreGraphicsPixelData::createImage (juceImage, colourSpace, mustOutliveSource);
|
||||
}
|
||||
|
||||
CGContextRef juce_getImageContext (const Image& image)
|
||||
{
|
||||
if (auto cgi = dynamic_cast<CoreGraphicsPixelData*> (image.getPixelData()))
|
||||
return cgi->context.get();
|
||||
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
#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];
|
||||
detail::ColorSpacePtr colourSpace { CGColorSpaceCreateWithName (kCGColorSpaceSRGB) };
|
||||
detail::ImagePtr imageRef { juce_createCoreGraphicsImage (image, colourSpace.get(), true) };
|
||||
|
||||
NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage: imageRef.get()];
|
||||
[imageRep setSize: requiredSize];
|
||||
[im addRepresentation: imageRep];
|
||||
[imageRep release];
|
||||
return im;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
108
deps/juce/modules/juce_graphics/native/juce_mac_CoreGraphicsHelpers.h
vendored
Normal file
108
deps/juce/modules/juce_graphics/native/juce_mac_CoreGraphicsHelpers.h
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 { (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 { (float) r.origin.x,
|
||||
(float) r.origin.y,
|
||||
(float) r.size.width,
|
||||
(float) 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 <class PointType>
|
||||
Point<float> convertToPointFloat (PointType p) noexcept
|
||||
{
|
||||
return { (float) p.x, (float) p.y };
|
||||
}
|
||||
|
||||
template <typename PointType>
|
||||
CGPoint convertToCGPoint (PointType p) noexcept
|
||||
{
|
||||
return CGPointMake ((CGFloat) p.x, (CGFloat) p.y);
|
||||
}
|
||||
|
||||
template <class PointType>
|
||||
Point<int> roundToIntPoint (PointType p) noexcept
|
||||
{
|
||||
return { roundToInt (p.x), roundToInt (p.y) };
|
||||
}
|
||||
|
||||
#if JUCE_MAC
|
||||
inline CGFloat getMainScreenHeight() noexcept
|
||||
{
|
||||
if ([[NSScreen screens] count] == 0)
|
||||
return 0.0f;
|
||||
|
||||
return [[[NSScreen screens] objectAtIndex: 0] frame].size.height;
|
||||
}
|
||||
|
||||
inline NSRect flippedScreenRect (NSRect r) noexcept
|
||||
{
|
||||
r.origin.y = getMainScreenHeight() - (r.origin.y + r.size.height);
|
||||
return r;
|
||||
}
|
||||
|
||||
inline NSPoint flippedScreenPoint (NSPoint p) noexcept
|
||||
{
|
||||
p.y = getMainScreenHeight() - p.y;
|
||||
return p;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
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
|
901
deps/juce/modules/juce_graphics/native/juce_mac_Fonts.mm
vendored
Normal file
901
deps/juce/modules/juce_graphics/native/juce_mac_Fonts.mm
vendored
Normal file
@ -0,0 +1,901 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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.1f, 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 CFUniquePtr<CTFontRef> getFontWithPointSize (CTFontRef font, float pointSize)
|
||||
{
|
||||
return CFUniquePtr<CTFontRef> (CTFontCreateCopyWithAttributes (font, pointSize, nullptr, nullptr));
|
||||
}
|
||||
|
||||
static CFUniquePtr<CTFontRef> createCTFont (const Font& font, const float fontSizePoints, CGAffineTransform& transformRequired)
|
||||
{
|
||||
CFUniquePtr<CFStringRef> cfFontFamily (FontStyleHelpers::getConcreteFamilyName (font).toCFString());
|
||||
CFUniquePtr<CFStringRef> cfFontStyle (findBestAvailableStyle (font, transformRequired).toCFString());
|
||||
CFStringRef keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute };
|
||||
CFTypeRef values[] = { cfFontFamily.get(), cfFontStyle.get() };
|
||||
|
||||
CFUniquePtr<CFDictionaryRef> fontDescAttributes (CFDictionaryCreate (nullptr,
|
||||
(const void**) &keys,
|
||||
(const void**) &values,
|
||||
numElementsInArray (keys),
|
||||
&kCFTypeDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks));
|
||||
|
||||
CFUniquePtr<CTFontDescriptorRef> ctFontDescRef (CTFontDescriptorCreateWithAttributes (fontDescAttributes.get()));
|
||||
|
||||
return CFUniquePtr<CTFontRef> (CTFontCreateWithFontDescriptor (ctFontDescRef.get(), fontSizePoints, nullptr));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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 CFUniquePtr<CTFontRef> getOrCreateFont (const Font& f)
|
||||
{
|
||||
if (auto ctf = getCTFontFromTypeface (f))
|
||||
{
|
||||
CFRetain (ctf);
|
||||
return CFUniquePtr<CTFontRef> (ctf);
|
||||
}
|
||||
|
||||
CGAffineTransform transform;
|
||||
return createCTFont (f, referenceFontSize, transform);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static CTTextAlignment getTextAlignment (const AttributedString& text)
|
||||
{
|
||||
const auto flags = text.getJustification().getOnlyHorizontalFlags();
|
||||
|
||||
if (@available (macOS 10.8, *))
|
||||
{
|
||||
switch (flags)
|
||||
{
|
||||
case Justification::right: return kCTTextAlignmentRight;
|
||||
case Justification::horizontallyCentred: return kCTTextAlignmentCenter;
|
||||
case Justification::horizontallyJustified: return kCTTextAlignmentJustified;
|
||||
default: return kCTTextAlignmentLeft;
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
|
||||
switch (flags)
|
||||
{
|
||||
case Justification::right: return kCTRightTextAlignment;
|
||||
case Justification::horizontallyCentred: return kCTCenterTextAlignment;
|
||||
case Justification::horizontallyJustified: return kCTJustifiedTextAlignment;
|
||||
default: return kCTLeftTextAlignment;
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
static CTLineBreakMode getLineBreakMode (const AttributedString& text)
|
||||
{
|
||||
switch (text.getWordWrap())
|
||||
{
|
||||
case AttributedString::none: return kCTLineBreakByClipping;
|
||||
case AttributedString::byChar: return kCTLineBreakByCharWrapping;
|
||||
case AttributedString::byWord:
|
||||
default: return kCTLineBreakByWordWrapping;
|
||||
}
|
||||
}
|
||||
|
||||
static CTWritingDirection getWritingDirection (const AttributedString& text)
|
||||
{
|
||||
switch (text.getReadingDirection())
|
||||
{
|
||||
case AttributedString::rightToLeft: return kCTWritingDirectionRightToLeft;
|
||||
case AttributedString::leftToRight: return kCTWritingDirectionLeftToRight;
|
||||
case AttributedString::natural:
|
||||
default: return kCTWritingDirectionNatural;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct AttributedStringAndFontMap
|
||||
{
|
||||
CFUniquePtr<CFAttributedStringRef> string;
|
||||
std::map<CTFontRef, Font> fontMap;
|
||||
};
|
||||
|
||||
static AttributedStringAndFontMap createCFAttributedString (const AttributedString& text)
|
||||
{
|
||||
std::map<CTFontRef, Font> fontMap;
|
||||
|
||||
const detail::ColorSpacePtr rgbColourSpace { CGColorSpaceCreateWithName (kCGColorSpaceSRGB) };
|
||||
|
||||
auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0);
|
||||
CFUniquePtr<CFStringRef> cfText (text.getText().toCFString());
|
||||
CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText.get());
|
||||
|
||||
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.get(), attr.font.getHeight() * getHeightToPointsFactor (ctFontRef.get()));
|
||||
fontMap.emplace (ctFontRef.get(), attr.font);
|
||||
|
||||
CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef.get());
|
||||
|
||||
if (attr.font.isUnderlined())
|
||||
{
|
||||
auto underline = kCTUnderlineStyleSingle;
|
||||
|
||||
CFUniquePtr<CFNumberRef> numberRef (CFNumberCreate (nullptr, kCFNumberIntType, &underline));
|
||||
CFAttributedStringSetAttribute (attribString, range, kCTUnderlineStyleAttributeName, numberRef.get());
|
||||
}
|
||||
|
||||
auto extraKerning = attr.font.getExtraKerningFactor();
|
||||
|
||||
if (extraKerning != 0)
|
||||
{
|
||||
extraKerning *= attr.font.getHeight();
|
||||
|
||||
CFUniquePtr<CFNumberRef> numberRef (CFNumberCreate (nullptr, kCFNumberFloatType, &extraKerning));
|
||||
CFAttributedStringSetAttribute (attribString, range, kCTKernAttributeName, numberRef.get());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto col = attr.colour;
|
||||
|
||||
const CGFloat components[] = { col.getFloatRed(),
|
||||
col.getFloatGreen(),
|
||||
col.getFloatBlue(),
|
||||
col.getFloatAlpha() };
|
||||
auto colour = CGColorCreate (rgbColourSpace.get(), components);
|
||||
|
||||
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 }
|
||||
};
|
||||
|
||||
CFUniquePtr<CTParagraphStyleRef> ctParagraphStyleRef (CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings)));
|
||||
CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)),
|
||||
kCTParagraphStyleAttributeName, ctParagraphStyleRef.get());
|
||||
return { CFUniquePtr<CFAttributedStringRef> (attribString), std::move (fontMap) };
|
||||
}
|
||||
|
||||
struct FramesetterAndFontMap
|
||||
{
|
||||
CFUniquePtr<CTFramesetterRef> framesetter;
|
||||
std::map<CTFontRef, Font> fontMap;
|
||||
};
|
||||
|
||||
static FramesetterAndFontMap createCTFramesetter (const AttributedString& text)
|
||||
{
|
||||
auto attribStringAndMap = createCFAttributedString (text);
|
||||
return { CFUniquePtr<CTFramesetterRef> (CTFramesetterCreateWithAttributedString (attribStringAndMap.string.get())),
|
||||
std::move (attribStringAndMap.fontMap) };
|
||||
}
|
||||
|
||||
static CFUniquePtr<CTFrameRef> createCTFrame (CTFramesetterRef framesetter, CGRect bounds)
|
||||
{
|
||||
auto path = CGPathCreateMutable();
|
||||
CGPathAddRect (path, nullptr, bounds);
|
||||
|
||||
CFUniquePtr<CTFrameRef> frame (CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr));
|
||||
CGPathRelease (path);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
struct FrameAndFontMap
|
||||
{
|
||||
CFUniquePtr<CTFrameRef> frame;
|
||||
std::map<CTFontRef, Font> fontMap;
|
||||
};
|
||||
|
||||
static FrameAndFontMap createCTFrame (const AttributedString& text, CGRect bounds)
|
||||
{
|
||||
auto framesetterAndMap = createCTFramesetter (text);
|
||||
return { createCTFrame (framesetterAndMap.framesetter.get(), bounds),
|
||||
std::move (framesetterAndMap.fontMap) };
|
||||
}
|
||||
|
||||
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 bool areAllFontsDefaultWidth (const AttributedString& text)
|
||||
{
|
||||
auto numCharacterAttributes = text.getNumAttributes();
|
||||
|
||||
for (int i = 0; i < numCharacterAttributes; ++i)
|
||||
if (text.getAttribute (i).font.getHorizontalScale() != 1.0f)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool drawToCGContext (const AttributedString& text, const Rectangle<float>& area,
|
||||
const CGContextRef& context, float flipHeight)
|
||||
{
|
||||
if (! areAllFontsDefaultWidth (text))
|
||||
return false;
|
||||
|
||||
auto framesetter = createCTFramesetter (text).framesetter;
|
||||
|
||||
// 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
|
||||
|
||||
CFRange fitrange;
|
||||
auto suggestedSingleLineFrameSize =
|
||||
CTFramesetterSuggestFrameSizeWithConstraints (framesetter.get(), CFRangeMake (0, 0), nullptr,
|
||||
CGSizeMake (CGFLOAT_MAX, CGFLOAT_MAX), &fitrange);
|
||||
auto minCTFrameHeight = (float) suggestedSingleLineFrameSize.height;
|
||||
|
||||
auto verticalJustification = text.getJustification().getOnlyVerticalFlags();
|
||||
|
||||
auto ctFrameArea = [area, minCTFrameHeight, verticalJustification]
|
||||
{
|
||||
if (minCTFrameHeight < area.getHeight())
|
||||
return area;
|
||||
|
||||
if (verticalJustification == Justification::verticallyCentred)
|
||||
return area.withSizeKeepingCentre (area.getWidth(), minCTFrameHeight);
|
||||
|
||||
auto frameArea = area.withHeight (minCTFrameHeight);
|
||||
|
||||
if (verticalJustification == Justification::bottom)
|
||||
return frameArea.withBottomY (area.getBottom());
|
||||
|
||||
return frameArea;
|
||||
}();
|
||||
|
||||
auto frame = createCTFrame (framesetter.get(), CGRectMake ((CGFloat) ctFrameArea.getX(), flipHeight - (CGFloat) ctFrameArea.getBottom(),
|
||||
(CGFloat) ctFrameArea.getWidth(), (CGFloat) ctFrameArea.getHeight()));
|
||||
|
||||
auto textMatrix = CGContextGetTextMatrix (context);
|
||||
CGContextSaveGState (context);
|
||||
|
||||
if (verticalJustification == Justification::verticallyCentred
|
||||
|| verticalJustification == Justification::bottom)
|
||||
{
|
||||
auto adjust = ctFrameArea.getHeight() - findCTFrameHeight (frame.get());
|
||||
|
||||
if (verticalJustification == Justification::verticallyCentred)
|
||||
adjust *= 0.5f;
|
||||
|
||||
CGContextTranslateCTM (context, 0, -adjust);
|
||||
}
|
||||
|
||||
CTFrameDraw (frame.get(), context);
|
||||
|
||||
CGContextRestoreGState (context);
|
||||
CGContextSetTextMatrix (context, textMatrix);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void createLayout (TextLayout& glyphLayout, const AttributedString& text)
|
||||
{
|
||||
auto boundsHeight = glyphLayout.getHeight();
|
||||
auto frameAndMap = createCTFrame (text, CGRectMake (0, 0, glyphLayout.getWidth(), boundsHeight));
|
||||
auto lines = CTFrameGetLines (frameAndMap.frame.get());
|
||||
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;
|
||||
Range<int> lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd);
|
||||
|
||||
LineInfo lineInfo (frameAndMap.frame.get(), line, i);
|
||||
|
||||
auto glyphLine = std::make_unique<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);
|
||||
|
||||
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))
|
||||
{
|
||||
glyphRun->font = [&]
|
||||
{
|
||||
auto it = frameAndMap.fontMap.find (ctRunFont);
|
||||
|
||||
if (it != frameAndMap.fontMap.end())
|
||||
return it->second;
|
||||
|
||||
CFUniquePtr<CFStringRef> cfsFontName (CTFontCopyPostScriptName (ctRunFont));
|
||||
CFUniquePtr<CTFontRef> ctFontRef (CTFontCreateWithName (cfsFontName.get(), referenceFontSize, nullptr));
|
||||
|
||||
auto fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef.get());
|
||||
|
||||
CFUniquePtr<CFStringRef> cfsFontFamily ((CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute));
|
||||
CFUniquePtr<CFStringRef> cfsFontStyle ((CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontStyleNameAttribute));
|
||||
|
||||
Font result (String::fromCFString (cfsFontFamily.get()),
|
||||
String::fromCFString (cfsFontStyle.get()),
|
||||
(float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor));
|
||||
|
||||
auto isUnderlined = [&]
|
||||
{
|
||||
CFNumberRef underlineStyle;
|
||||
|
||||
if (CFDictionaryGetValueIfPresent (runAttributes, kCTUnderlineStyleAttributeName, (const void**) &underlineStyle))
|
||||
{
|
||||
if (CFGetTypeID (underlineStyle) == CFNumberGetTypeID())
|
||||
{
|
||||
int value = 0;
|
||||
CFNumberGetValue (underlineStyle, kCFNumberLongType, (void*) &value);
|
||||
|
||||
return value != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}();
|
||||
|
||||
result.setUnderline (isUnderlined);
|
||||
return result;
|
||||
}();
|
||||
}
|
||||
|
||||
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],
|
||||
convertToPointFloat (positions.points[k]),
|
||||
(float) advances.advances[k].width));
|
||||
}
|
||||
|
||||
glyphLayout.addLine (std::move (glyphLine));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
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.get(), 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
|
||||
CFUniquePtr<CFDataRef> cfData (CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) dataCopy.getData(),
|
||||
(CFIndex) dataCopy.getSize(), kCFAllocatorNull));
|
||||
auto provider = CGDataProviderCreateWithCFData (cfData.get());
|
||||
|
||||
#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 (@available (macOS 10.11, *))
|
||||
canBeUsedForLayout = CTFontManagerRegisterGraphicsFont (fontRef, nullptr);
|
||||
|
||||
ctFontRef.reset (CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr));
|
||||
|
||||
if (ctFontRef != nullptr)
|
||||
{
|
||||
if (auto fontName = CFUniquePtr<CFStringRef> (CTFontCopyName (ctFontRef.get(), kCTFontFamilyNameKey)))
|
||||
name = String::fromCFString (fontName.get());
|
||||
|
||||
if (auto fontStyle = CFUniquePtr<CFStringRef> (CTFontCopyName (ctFontRef.get(), kCTFontStyleNameKey)))
|
||||
style = String::fromCFString (fontStyle.get());
|
||||
|
||||
initialiseMetrics();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void initialiseMetrics()
|
||||
{
|
||||
auto ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef.get()));
|
||||
auto ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef.get()));
|
||||
auto ctTotalHeight = ctAscent + ctDescent;
|
||||
|
||||
ascent = ctAscent / ctTotalHeight;
|
||||
unitsToHeightScaleFactor = 1.0f / ctTotalHeight;
|
||||
pathTransform = AffineTransform::scale (unitsToHeightScaleFactor);
|
||||
|
||||
fontHeightToPointsFactor = referenceFontSize / ctTotalHeight;
|
||||
|
||||
const short zero = 0;
|
||||
CFUniquePtr<CFNumberRef> numberRef (CFNumberCreate (nullptr, kCFNumberShortType, &zero));
|
||||
|
||||
CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName };
|
||||
CFTypeRef values[] = { ctFontRef.get(), numberRef.get() };
|
||||
attributedStringAtts.reset (CFDictionaryCreate (nullptr, (const void**) &keys,
|
||||
(const void**) &values, numElementsInArray (keys),
|
||||
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
|
||||
}
|
||||
|
||||
~OSXTypeface() override
|
||||
{
|
||||
if (fontRef != nullptr)
|
||||
{
|
||||
if (@available (macOS 10.8, *))
|
||||
if (dataCopy.getSize() != 0)
|
||||
CTFontManagerUnregisterGraphicsFont (fontRef, nullptr);
|
||||
|
||||
CGFontRelease (fontRef);
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
CFUniquePtr<CFStringRef> cfText (text.toCFString());
|
||||
CFUniquePtr<CFAttributedStringRef> attribString (CFAttributedStringCreate (kCFAllocatorDefault, cfText.get(), attributedStringAtts.get()));
|
||||
|
||||
CFUniquePtr<CTLineRef> line (CTLineCreateWithAttributedString (attribString.get()));
|
||||
auto runArray = CTLineGetGlyphRuns (line.get());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
CFUniquePtr<CFStringRef> cfText (text.toCFString());
|
||||
CFUniquePtr<CFAttributedStringRef> attribString (CFAttributedStringCreate (kCFAllocatorDefault, cfText.get(), attributedStringAtts.get()));
|
||||
|
||||
CFUniquePtr<CTLineRef> line (CTLineCreateWithAttributedString (attribString.get()));
|
||||
auto runArray = CTLineGetGlyphRuns (line.get());
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = CFUniquePtr<CGPathRef> (CTFontCreatePathForGlyph (ctFontRef.get(), (CGGlyph) glyphNumber, &renderingTransform)))
|
||||
{
|
||||
CGPathApply (pathRef.get(), &path, pathApplier);
|
||||
|
||||
if (! pathTransform.isIdentity())
|
||||
path.applyTransform (pathTransform);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
CGFontRef fontRef = {};
|
||||
CFUniquePtr<CTFontRef> ctFontRef;
|
||||
|
||||
float fontHeightToPointsFactor = 1.0f;
|
||||
CGAffineTransform renderingTransform = CGAffineTransformIdentity;
|
||||
|
||||
bool canBeUsedForLayout;
|
||||
|
||||
private:
|
||||
MemoryBlock dataCopy;
|
||||
CFUniquePtr<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)
|
||||
{
|
||||
const auto typeface = f.getTypefacePtr();
|
||||
|
||||
if (auto* tf = dynamic_cast<OSXTypeface*> (typeface.get()))
|
||||
return tf->ctFontRef.get();
|
||||
|
||||
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
|
||||
CFUniquePtr<CFArrayRef> fontFamilyArray (CTFontManagerCopyAvailableFontFamilyNames());
|
||||
|
||||
for (CFIndex i = 0; i < CFArrayGetCount (fontFamilyArray.get()); ++i)
|
||||
{
|
||||
auto family = String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (fontFamilyArray.get(), i));
|
||||
|
||||
if (! family.startsWithChar ('.')) // ignore fonts that start with a '.'
|
||||
names.addIfNotAlreadyThere (family);
|
||||
}
|
||||
#else
|
||||
CFUniquePtr<CTFontCollectionRef> fontCollectionRef (CTFontCollectionCreateFromAvailableFonts (nullptr));
|
||||
CFUniquePtr<CFArrayRef> fontDescriptorArray (CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef.get()));
|
||||
|
||||
for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray.get()); ++i)
|
||||
{
|
||||
auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray.get(), i);
|
||||
CFUniquePtr<CFStringRef> cfsFontFamily ((CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontFamilyNameAttribute));
|
||||
|
||||
names.addIfNotAlreadyThere (String::fromCFString (cfsFontFamily.get()));
|
||||
}
|
||||
#endif
|
||||
|
||||
names.sort (true);
|
||||
return names;
|
||||
}
|
||||
|
||||
StringArray Font::findAllTypefaceStyles (const String& family)
|
||||
{
|
||||
if (FontStyleHelpers::isPlaceholderFamilyName (family))
|
||||
return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));
|
||||
|
||||
StringArray results;
|
||||
|
||||
CFUniquePtr<CFStringRef> cfsFontFamily (family.toCFString());
|
||||
CFStringRef keys[] = { kCTFontFamilyNameAttribute };
|
||||
CFTypeRef values[] = { cfsFontFamily.get() };
|
||||
|
||||
CFUniquePtr<CFDictionaryRef> fontDescAttributes (CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys),
|
||||
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
|
||||
|
||||
CFUniquePtr<CTFontDescriptorRef> ctFontDescRef (CTFontDescriptorCreateWithAttributes (fontDescAttributes.get()));
|
||||
CFUniquePtr<CFArrayRef> fontFamilyArray (CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks));
|
||||
|
||||
CFUniquePtr<CTFontCollectionRef> fontCollectionRef (CTFontCollectionCreateWithFontDescriptors (fontFamilyArray.get(), nullptr));
|
||||
|
||||
if (auto fontDescriptorArray = CFUniquePtr<CFArrayRef> (CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef.get())))
|
||||
{
|
||||
for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray.get()); ++i)
|
||||
{
|
||||
auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray.get(), i);
|
||||
CFUniquePtr<CFStringRef> cfsFontStyle ((CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontStyleNameAttribute));
|
||||
results.add (String::fromCFString (cfsFontStyle.get()));
|
||||
}
|
||||
}
|
||||
|
||||
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& folder)
|
||||
{
|
||||
for (auto& file : folder.findChildFiles (File::findFiles, false, "*.otf;*.ttf"))
|
||||
if (auto urlref = CFUniquePtr<CFURLRef> (CFURLCreateWithFileSystemPath (kCFAllocatorDefault, file.getFullPathName().toCFString(), kCFURLPOSIXPathStyle, true)))
|
||||
CTFontManagerRegisterFontsForURL (urlref.get(), kCTFontManagerScopeProcess, nullptr);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
auto typeface = text.getAttribute (i).font.getTypefacePtr();
|
||||
|
||||
if (auto tf = dynamic_cast<OSXTypeface*> (typeface.get()))
|
||||
if (tf->canBeUsedForLayout)
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextLayout::createNativeLayout (const AttributedString& text)
|
||||
{
|
||||
if (canAllTypefacesBeUsedInLayout (text) && CoreTextTypeLayout::areAllFontsDefaultWidth (text))
|
||||
{
|
||||
CoreTextTypeLayout::createLayout (*this, text);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
141
deps/juce/modules/juce_graphics/native/juce_mac_IconHelpers.cpp
vendored
Normal file
141
deps/juce/modules/juce_graphics/native/juce_mac_IconHelpers.cpp
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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)
|
||||
{
|
||||
if (auto pathCFString = CFUniquePtr<CFStringRef> (CFStringCreateWithCString (kCFAllocatorDefault, applicationPath.toRawUTF8(), kCFStringEncodingUTF8)))
|
||||
{
|
||||
if (auto url = CFUniquePtr<CFURLRef> (CFURLCreateWithFileSystemPath (kCFAllocatorDefault, pathCFString.get(), kCFURLPOSIXPathStyle, 1)))
|
||||
{
|
||||
if (auto appBundle = CFUniquePtr<CFBundleRef> (CFBundleCreate (kCFAllocatorDefault, url.get())))
|
||||
{
|
||||
if (CFTypeRef infoValue = CFBundleGetValueForInfoDictionaryKey (appBundle.get(), CFSTR("CFBundleIconFile")))
|
||||
{
|
||||
if (CFGetTypeID (infoValue) == CFStringGetTypeID())
|
||||
{
|
||||
CFStringRef iconFilename = reinterpret_cast<CFStringRef> (infoValue);
|
||||
CFStringRef resourceURLSuffix = CFStringHasSuffix (iconFilename, CFSTR(".icns")) ? nullptr : CFSTR("icns");
|
||||
|
||||
if (auto iconURL = CFUniquePtr<CFURLRef> (CFBundleCopyResourceURL (appBundle.get(), iconFilename, resourceURLSuffix, nullptr)))
|
||||
{
|
||||
if (auto iconPath = CFUniquePtr<CFStringRef> (CFURLCopyFileSystemPath (iconURL.get(), kCFURLPOSIXPathStyle)))
|
||||
{
|
||||
File icnsFile (CFStringGetCStringPtr (iconPath.get(), CFStringGetSystemEncoding()));
|
||||
return getIconFromIcnsFile (icnsFile, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace juce
|
780
deps/juce/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.cpp
vendored
Normal file
780
deps/juce/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.cpp
vendored
Normal file
@ -0,0 +1,780 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 { (float) r.getX(), (float) r.getY(), (float) r.getRight(), (float) r.getBottom() };
|
||||
}
|
||||
|
||||
static D2D1_COLOR_F colourToD2D (Colour c)
|
||||
{
|
||||
return { 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:
|
||||
{
|
||||
transform.transformPoint (it.x1, it.y1);
|
||||
transform.transformPoint (it.x2, it.y2);
|
||||
transform.transformPoint (it.x3, it.y3);
|
||||
|
||||
sink->AddBezier ({ { it.x1, it.y1 }, { it.x2, it.y2 }, { it.x3, it.y3 } });
|
||||
break;
|
||||
}
|
||||
|
||||
case Path::Iterator::lineTo:
|
||||
{
|
||||
transform.transformPoint (it.x1, it.y1);
|
||||
sink->AddLine ({ it.x1, it.y1 });
|
||||
break;
|
||||
}
|
||||
|
||||
case Path::Iterator::quadraticTo:
|
||||
{
|
||||
transform.transformPoint (it.x1, it.y1);
|
||||
transform.transformPoint (it.x2, it.y2);
|
||||
sink->AddQuadraticBezier ({ { it.x1, it.y1 }, { it.x2, it.y2 } });
|
||||
break;
|
||||
}
|
||||
|
||||
case Path::Iterator::closePath:
|
||||
{
|
||||
sink->EndFigure (D2D1_FIGURE_END_CLOSED);
|
||||
break;
|
||||
}
|
||||
|
||||
case Path::Iterator::startNewSubPath:
|
||||
{
|
||||
transform.transformPoint (it.x1, it.y1);
|
||||
sink->BeginFigure ({ it.x1, it.y1 }, D2D1_FIGURE_BEGIN_FILLED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static D2D1::Matrix3x2F transformToMatrix (const AffineTransform& transform)
|
||||
{
|
||||
return { transform.mat00, transform.mat10, transform.mat01, transform.mat11, transform.mat02, transform.mat12 };
|
||||
}
|
||||
|
||||
static D2D1_POINT_2F pointTransformed (int x, int y, const AffineTransform& transform)
|
||||
{
|
||||
transform.transformPoint (x, y);
|
||||
return { (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;
|
||||
auto 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;
|
||||
auto 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 auto 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().transformedBy (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 = { 1, transformToMatrix (clipTransform) };
|
||||
auto bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP);
|
||||
D2D1_SIZE_U size = { (UINT32) clipImage.getWidth(), (UINT32) clipImage.getHeight() };
|
||||
auto 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;
|
||||
|
||||
auto 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)
|
||||
{
|
||||
auto layerParams = D2D1::LayerParameters();
|
||||
rectListGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds);
|
||||
layerParams.geometricMask = rectListGeometry;
|
||||
owner.pimpl->renderingTarget->PushLayer (layerParams, rectListLayer);
|
||||
clipsRectList = true;
|
||||
}
|
||||
|
||||
if (shouldClipComplex && !clipsComplex)
|
||||
{
|
||||
auto 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)
|
||||
{
|
||||
auto typefacePtr = font.getTypefacePtr();
|
||||
auto* typeface = dynamic_cast<WindowsDirectWriteTypeface*> (typefacePtr.get());
|
||||
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())
|
||||
{
|
||||
auto colour = colourToD2D (fillType.colour);
|
||||
owner.pimpl->colourBrush->SetColor (colour);
|
||||
currentBrush = owner.pimpl->colourBrush;
|
||||
}
|
||||
else if (fillType.isTiledImage())
|
||||
{
|
||||
D2D1_BRUSH_PROPERTIES brushProps = { fillType.getOpacity(), transformToMatrix (fillType.transform) };
|
||||
auto bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP);
|
||||
|
||||
image = fillType.image;
|
||||
|
||||
D2D1_SIZE_U size = { (UINT32) image.getWidth(), (UINT32) image.getHeight() };
|
||||
auto 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;
|
||||
|
||||
auto 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 = { fillType.getOpacity(), 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 auto p1 = fillType.gradient->point1;
|
||||
const auto p2 = fillType.gradient->point2;
|
||||
const auto r = p1.getDistanceFrom(p2);
|
||||
const auto props = D2D1::RadialGradientBrushProperties ({ p1.x, p1.y }, {}, r, r);
|
||||
|
||||
owner.pimpl->renderingTarget->CreateRadialGradientBrush (props, brushProps, gradientStops, radialGradient.resetAndGetPointerAddress());
|
||||
currentBrush = radialGradient;
|
||||
}
|
||||
else
|
||||
{
|
||||
linearGradient = 0;
|
||||
|
||||
const auto p1 = fillType.gradient->point1;
|
||||
const auto p2 = fillType.gradient->point2;
|
||||
const auto props = D2D1::LinearGradientBrushProperties ({ p1.x, p1.y }, { p2.x, p2.y });
|
||||
|
||||
owner.pimpl->renderingTarget->CreateLinearGradientBrush (props, brushProps, gradientStops, linearGradient.resetAndGetPointerAddress());
|
||||
|
||||
currentBrush = linearGradient;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Direct2DLowLevelGraphicsContext& owner;
|
||||
|
||||
AffineTransform transform;
|
||||
|
||||
Font font;
|
||||
float fontHeightToEmSizeFactor = 1.0f;
|
||||
|
||||
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);
|
||||
|
||||
if (pimpl->factories->d2dFactory != nullptr)
|
||||
{
|
||||
auto hr = pimpl->factories->d2dFactory->CreateHwndRenderTarget ({}, { hwnd, size }, 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 = nullptr;
|
||||
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 std::sqrt (std::abs (currentState->transform.getDeterminant()));
|
||||
}
|
||||
|
||||
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().transformedBy (currentState->transform).getSmallestIntegerContainer());
|
||||
}
|
||||
|
||||
Rectangle<int> Direct2DLowLevelGraphicsContext::getClipBounds() const
|
||||
{
|
||||
// xxx could this take into account complex clip regions?
|
||||
return currentState->clipRect.toFloat().transformedBy (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 = { (UINT32) image.getWidth(), (UINT32) image.getHeight() };
|
||||
auto 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();
|
||||
|
||||
auto hScale = currentState->font.getHorizontalScale();
|
||||
|
||||
pimpl->renderingTarget->SetTransform (transformToMatrix (AffineTransform::scale (hScale, 1.0f)
|
||||
.followedBy (transform)
|
||||
.followedBy (currentState->transform)));
|
||||
|
||||
const auto glyphIndices = (UINT16) glyphNumber;
|
||||
const auto glyphAdvances = 0.0f;
|
||||
DWRITE_GLYPH_OFFSET offset = { 0.0f, 0.0f };
|
||||
|
||||
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 ({}, &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
|
103
deps/juce/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.h
vendored
Normal file
103
deps/juce/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.h
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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;
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DLowLevelGraphicsContext)
|
||||
};
|
||||
|
||||
} // namespace juce
|
485
deps/juce/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp
vendored
Normal file
485
deps/juce/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp
vendored
Normal file
@ -0,0 +1,485 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 (0),
|
||||
attributedString (as),
|
||||
fontCollection (fonts)
|
||||
{
|
||||
}
|
||||
|
||||
JUCE_COMRESULT QueryInterface (REFIID refId, void** result) override
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
if (refId == __uuidof (IDWritePixelSnapping))
|
||||
return castToType<IDWritePixelSnapping> (result);
|
||||
|
||||
return ComBaseClassHelper<IDWriteTextRenderer>::QueryInterface (refId, result);
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
JUCE_COMRESULT IsPixelSnappingDisabled (void* /*clientDrawingContext*/, BOOL* isDisabled) noexcept override
|
||||
{
|
||||
*isDisabled = FALSE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetCurrentTransform (void*, DWRITE_MATRIX* matrix) noexcept 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) noexcept override
|
||||
{
|
||||
*pixelsPerDip = 1.0f;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT DrawUnderline (void*, FLOAT, FLOAT, DWRITE_UNDERLINE const*, IUnknown*) noexcept override
|
||||
{
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT DrawStrikethrough (void*, FLOAT, FLOAT, DWRITE_STRIKETHROUGH const*, IUnknown*) noexcept override
|
||||
{
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT DrawInlineObject (void*, FLOAT, FLOAT, IDWriteInlineObject*, BOOL, BOOL, IUnknown*) noexcept 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) noexcept override
|
||||
{
|
||||
const auto containsTextOrNewLines = [runDescription]
|
||||
{
|
||||
const String runString (runDescription->string, runDescription->stringLength);
|
||||
|
||||
if (runString.containsNonWhitespaceChars() || runString.containsAnyOf ("\n\r"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (! containsTextOrNewLines)
|
||||
return S_OK;
|
||||
|
||||
auto 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());
|
||||
auto line = std::make_unique<TextLayout::Line>();
|
||||
line->lineOrigin = Point<float> (baselineOriginX, baselineOriginY);
|
||||
|
||||
layout->addLine (std::move (line));
|
||||
}
|
||||
}
|
||||
|
||||
auto& 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));
|
||||
|
||||
auto glyphRunLayout = new TextLayout::Run (Range<int> ((int) runDescription->textPosition,
|
||||
(int) (runDescription->textPosition + runDescription->stringLength)),
|
||||
(int) glyphRun->glyphCount);
|
||||
glyphLine.runs.add (glyphRunLayout);
|
||||
|
||||
glyphRun->fontFace->GetMetrics (&dwFontMetrics);
|
||||
auto totalHeight = std::abs ((float) dwFontMetrics.ascent) + std::abs ((float) dwFontMetrics.descent);
|
||||
auto fontHeightToEmSizeFactor = (float) dwFontMetrics.designUnitsPerEm / totalHeight;
|
||||
|
||||
glyphRunLayout->font = getFontForRun (*glyphRun, glyphRun->fontEmSize / fontHeightToEmSizeFactor);
|
||||
glyphRunLayout->colour = getColourOf (static_cast<ID2D1SolidColorBrush*> (clientDrawingEffect));
|
||||
|
||||
auto lineOrigin = layout->getLine (currentLine).lineOrigin;
|
||||
auto x = baselineOriginX - lineOrigin.x;
|
||||
|
||||
auto extraKerning = glyphRunLayout->font.getExtraKerningFactor()
|
||||
* glyphRunLayout->font.getHeight();
|
||||
|
||||
for (UINT32 i = 0; i < glyphRun->glyphCount; ++i)
|
||||
{
|
||||
auto 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 = -1;
|
||||
float lastOriginY = -10000.0f;
|
||||
|
||||
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)
|
||||
{
|
||||
auto& font = attributedString.getAttribute(i).font;
|
||||
auto typeface = font.getTypefacePtr();
|
||||
|
||||
if (auto* wt = dynamic_cast<WindowsDirectWriteTypeface*> (typeface.get()))
|
||||
if (wt->getIDWriteFontFace() == glyphRun.fontFace)
|
||||
return font.withHeight (fontHeight);
|
||||
}
|
||||
|
||||
ComSmartPtr<IDWriteFont> dwFont;
|
||||
auto 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) (dwFontMetrics.ascent + 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 0:
|
||||
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 0:
|
||||
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 = (UINT32) attr.range.getStart();
|
||||
range.length = (UINT32) jmin (attr.range.getLength(), textLen - attr.range.getStart());
|
||||
|
||||
{
|
||||
auto familyName = FontStyleHelpers::getConcreteFamilyName (attr.font);
|
||||
|
||||
BOOL fontFound = false;
|
||||
uint32 fontIndex;
|
||||
fontCollection.FindFamilyName (familyName.toWideCharPointer(), &fontIndex, &fontFound);
|
||||
|
||||
if (! fontFound)
|
||||
fontIndex = 0;
|
||||
|
||||
ComSmartPtr<IDWriteFontFamily> fontFamily;
|
||||
auto hr = fontCollection.GetFontFamily (fontIndex, fontFamily.resetAndGetPointerAddress());
|
||||
|
||||
ComSmartPtr<IDWriteFont> dwFont;
|
||||
uint32 fontFacesCount = 0;
|
||||
fontFacesCount = fontFamily->GetFontCount();
|
||||
|
||||
for (int i = (int) fontFacesCount; --i >= 0;)
|
||||
{
|
||||
hr = fontFamily->GetFont ((UINT32) 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);
|
||||
|
||||
auto fontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (*dwFont);
|
||||
textLayout.SetFontSize (attr.font.getHeight() * fontHeightToEmSizeFactor, range);
|
||||
}
|
||||
|
||||
{
|
||||
auto 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 legitimate brush to get DirectWrite to break text based on colours
|
||||
textLayout.SetDrawingEffect (d2dBrush, range);
|
||||
}
|
||||
}
|
||||
|
||||
bool setupLayout (const AttributedString& text, float maxWidth, 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.getTypefacePtr()->getName().toWideCharPointer(), &fontIndex, &fontFound);
|
||||
|
||||
if (! fontFound)
|
||||
fontIndex = 0;
|
||||
|
||||
ComSmartPtr<IDWriteFontFamily> dwFontFamily;
|
||||
auto 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);
|
||||
|
||||
auto 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);
|
||||
}
|
||||
|
||||
auto textLen = text.getText().length();
|
||||
|
||||
hr = directWriteFactory.CreateTextLayout (text.getText().toWideCharPointer(), (UINT32) textLen, dwTextFormat,
|
||||
maxWidth, maxHeight, textLayout.resetAndGetPointerAddress());
|
||||
|
||||
if (FAILED (hr) || textLayout == nullptr)
|
||||
return false;
|
||||
|
||||
auto 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;
|
||||
auto hr = dwTextLayout->GetLineMetrics (nullptr, 0, &actualLineCount);
|
||||
|
||||
layout.ensureStorageAllocated ((int) 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;
|
||||
auto numLines = jmin ((int) actualLineCount, layout.getNumLines());
|
||||
float yAdjustment = 0;
|
||||
auto extraLineSpacing = text.getLineSpacing();
|
||||
|
||||
for (int i = 0; i < numLines; ++i)
|
||||
{
|
||||
auto& line = layout.getLine (i);
|
||||
line.stringRange = Range<int> (lastLocation, lastLocation + (int) 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 canAllTypefacesAndFontsBeUsedInLayout (const AttributedString& text)
|
||||
{
|
||||
auto numCharacterAttributes = text.getNumAttributes();
|
||||
|
||||
for (int i = 0; i < numCharacterAttributes; ++i)
|
||||
{
|
||||
const auto& font = text.getAttribute (i).font;
|
||||
auto typeface = font.getTypefacePtr();
|
||||
|
||||
if (font.getHorizontalScale() != 1.0f || dynamic_cast<WindowsDirectWriteTypeface*> (typeface.get()) == nullptr)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool TextLayout::createNativeLayout (const AttributedString& text)
|
||||
{
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
if (! canAllTypefacesAndFontsBeUsedInLayout (text))
|
||||
return false;
|
||||
|
||||
SharedResourcePointer<Direct2DFactories> factories;
|
||||
|
||||
if (factories->d2dFactory != nullptr
|
||||
&& factories->directWriteFactory != nullptr
|
||||
&& factories->systemFonts != nullptr
|
||||
&& factories->directWriteRenderTarget != nullptr)
|
||||
{
|
||||
DirectWriteTypeLayout::createLayout (*this, text,
|
||||
*factories->directWriteFactory,
|
||||
*factories->systemFonts,
|
||||
*factories->directWriteRenderTarget);
|
||||
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
ignoreUnused (text);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
335
deps/juce/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp
vendored
Normal file
335
deps/juce/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp
vendored
Normal file
@ -0,0 +1,335 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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;
|
||||
auto 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;
|
||||
auto hr = family->GetFamilyNames (familyNames.resetAndGetPointerAddress());
|
||||
jassert (SUCCEEDED (hr)); ignoreUnused (hr);
|
||||
return getLocalisedName (familyNames);
|
||||
}
|
||||
|
||||
static String getFontFaceName (IDWriteFont* font)
|
||||
{
|
||||
jassert (font != nullptr);
|
||||
ComSmartPtr<IDWriteLocalizedStrings> faceNames;
|
||||
auto 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()
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
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)
|
||||
{
|
||||
auto 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());
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
~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())
|
||||
{
|
||||
jassert (fontCollection != nullptr);
|
||||
|
||||
uint32 fontIndex = 0;
|
||||
auto 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 ((UINT32) 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);
|
||||
auto totalSize = ascent + std::abs ((float) dwFontMetrics.descent);
|
||||
ascent /= totalSize;
|
||||
unitsToHeightScaleFactor = (float) designUnitsPerEm / totalSize;
|
||||
|
||||
auto tempDC = GetDC (nullptr);
|
||||
auto dpi = (float) (GetDeviceCaps (tempDC, LOGPIXELSX) + GetDeviceCaps (tempDC, LOGPIXELSY)) / 2.0f;
|
||||
heightToPointsFactor = (dpi / (float) GetDeviceCaps (tempDC, LOGPIXELSY)) * unitsToHeightScaleFactor;
|
||||
ReleaseDC (nullptr, tempDC);
|
||||
|
||||
auto pathAscent = (1024.0f * dwFontMetrics.ascent) / (float) designUnitsPerEm;
|
||||
auto pathDescent = (1024.0f * dwFontMetrics.descent) / (float) designUnitsPerEm;
|
||||
auto pathScale = 1.0f / (std::abs (pathAscent) + std::abs (pathDescent));
|
||||
pathTransform = AffineTransform::scale (pathScale);
|
||||
}
|
||||
}
|
||||
|
||||
bool loadedOk() const noexcept { return dwFontFace != nullptr; }
|
||||
BOOL isFontFound() const noexcept { return fontFound; }
|
||||
|
||||
float getAscent() const { return ascent; }
|
||||
float getDescent() const { return 1.0f - ascent; }
|
||||
float getHeightToPointsFactor() const { return heightToPointsFactor; }
|
||||
|
||||
float getStringWidth (const String& text)
|
||||
{
|
||||
auto textUTF32 = text.toUTF32();
|
||||
auto 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 / (float) designUnitsPerEm;
|
||||
|
||||
return x * unitsToHeightScaleFactor;
|
||||
}
|
||||
|
||||
void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets)
|
||||
{
|
||||
xOffsets.add (0);
|
||||
|
||||
auto textUTF32 = text.toUTF32();
|
||||
auto 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 / (float) 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
|
||||
auto 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 = 1.0f, heightToPointsFactor = 1.0f, ascent = 0;
|
||||
int designUnitsPerEm = 0;
|
||||
AffineTransform pathTransform;
|
||||
BOOL fontFound = false;
|
||||
|
||||
struct PathGeometrySink : public ComBaseClassHelper<IDWriteGeometrySink>
|
||||
{
|
||||
PathGeometrySink() : ComBaseClassHelper (0) {}
|
||||
|
||||
void STDMETHODCALLTYPE AddBeziers (const D2D1_BEZIER_SEGMENT* beziers, UINT beziersCount) noexcept override
|
||||
{
|
||||
for (UINT i = 0; i < beziersCount; ++i)
|
||||
path.cubicTo (convertPoint (beziers[i].point1),
|
||||
convertPoint (beziers[i].point2),
|
||||
convertPoint (beziers[i].point3));
|
||||
}
|
||||
|
||||
void STDMETHODCALLTYPE AddLines (const D2D1_POINT_2F* points, UINT pointsCount) noexcept override
|
||||
{
|
||||
for (UINT i = 0; i < pointsCount; ++i)
|
||||
path.lineTo (convertPoint (points[i]));
|
||||
}
|
||||
|
||||
void STDMETHODCALLTYPE BeginFigure (D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN) noexcept override
|
||||
{
|
||||
path.startNewSubPath (convertPoint (startPoint));
|
||||
}
|
||||
|
||||
void STDMETHODCALLTYPE EndFigure (D2D1_FIGURE_END figureEnd) noexcept override
|
||||
{
|
||||
if (figureEnd == D2D1_FIGURE_END_CLOSED)
|
||||
path.closeSubPath();
|
||||
}
|
||||
|
||||
void STDMETHODCALLTYPE SetFillMode (D2D1_FILL_MODE fillMode) noexcept override
|
||||
{
|
||||
path.setUsingNonZeroWinding (fillMode == D2D1_FILL_MODE_WINDING);
|
||||
}
|
||||
|
||||
void STDMETHODCALLTYPE SetSegmentFlags (D2D1_PATH_SEGMENT) noexcept override {}
|
||||
JUCE_COMRESULT Close() noexcept 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
|
633
deps/juce/modules/juce_graphics/native/juce_win32_Fonts.cpp
vendored
Normal file
633
deps/juce/modules/juce_graphics/native/juce_win32_Fonts.cpp
vendored
Normal file
@ -0,0 +1,633 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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;
|
||||
auto oldPos = input.getPosition();
|
||||
input.setPosition (directoryOffset + offsetOfStringStorage + ByteOrder::swapIfLittleEndian (nameRecord.offsetFromStorageArea));
|
||||
auto stringLength = (int) ByteOrder::swapIfLittleEndian (nameRecord.stringLength);
|
||||
auto platformID = ByteOrder::swapIfLittleEndian (nameRecord.platformID);
|
||||
|
||||
if (platformID == 0 || platformID == 3)
|
||||
{
|
||||
auto 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 = {};
|
||||
input.read (&namingTable, sizeof (namingTable));
|
||||
|
||||
for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (namingTable.numberOfNameRecords); ++i)
|
||||
{
|
||||
NameRecord nameRecord = {};
|
||||
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 = {};
|
||||
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 = {};
|
||||
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));
|
||||
|
||||
auto dc = CreateCompatibleDC (nullptr);
|
||||
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)
|
||||
{
|
||||
auto hr = factories->systemFonts->GetFontFamily (i, fontFamily.resetAndGetPointerAddress());
|
||||
|
||||
if (SUCCEEDED (hr))
|
||||
results.addIfNotAlreadyThere (getFontFamilyName (fontFamily));
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
auto dc = CreateCompatibleDC (nullptr);
|
||||
|
||||
{
|
||||
LOGFONTW lf = {};
|
||||
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;
|
||||
auto 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);
|
||||
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);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class WindowsTypeface : public Typeface
|
||||
{
|
||||
public:
|
||||
WindowsTypeface (const Font& font) : Typeface (font.getTypefaceName(),
|
||||
font.getTypefaceStyle())
|
||||
{
|
||||
loadFont();
|
||||
}
|
||||
|
||||
WindowsTypeface (const void* data, size_t dataSize)
|
||||
: Typeface (String(), String())
|
||||
{
|
||||
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 != nullptr)
|
||||
DeleteObject (fontH);
|
||||
|
||||
if (memoryFont != nullptr)
|
||||
RemoveFontMemResourceEx (memoryFont);
|
||||
}
|
||||
|
||||
float getAscent() const { return ascent; }
|
||||
float getDescent() const { return 1.0f - ascent; }
|
||||
float getHeightToPointsFactor() const { return heightToPointsFactor; }
|
||||
|
||||
float getStringWidth (const String& text)
|
||||
{
|
||||
auto utf16 = text.toUTF16();
|
||||
auto 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)
|
||||
{
|
||||
auto utf16 = text.toUTF16();
|
||||
auto 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..)
|
||||
auto bufSize = (int) GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX,
|
||||
&gm, 0, nullptr, &identityMatrix);
|
||||
|
||||
if (bufSize > 0)
|
||||
{
|
||||
HeapBlock<char> data (bufSize);
|
||||
GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm,
|
||||
(DWORD) bufSize, data, &identityMatrix);
|
||||
|
||||
auto pheader = reinterpret_cast<const TTPOLYGONHEADER*> (data.getData());
|
||||
|
||||
auto scaleX = 1.0f / (float) tm.tmHeight;
|
||||
auto scaleY = -scaleX;
|
||||
|
||||
while ((char*) pheader < data + bufSize)
|
||||
{
|
||||
glyphPath.startNewSubPath (scaleX * pheader->pfxStart.x.value,
|
||||
scaleY * pheader->pfxStart.y.value);
|
||||
|
||||
auto curve = unalignedPointerCast<const TTPOLYCURVE*> ((const char*) pheader + sizeof (TTPOLYGONHEADER));
|
||||
auto 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)
|
||||
{
|
||||
auto x2 = scaleX * curve->apfx[i].x.value;
|
||||
auto y2 = scaleY * curve->apfx[i].y.value;
|
||||
auto x3 = scaleX * curve->apfx[i + 1].x.value;
|
||||
auto 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 = unalignedPointerCast<const TTPOLYGONHEADER*> (curve);
|
||||
|
||||
glyphPath.closeSubPath();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
static const MAT2 identityMatrix;
|
||||
HFONT fontH = {};
|
||||
HGDIOBJ previousFontH = {};
|
||||
HDC dc { CreateCompatibleDC (nullptr) };
|
||||
TEXTMETRIC tm;
|
||||
HANDLE memoryFont = {};
|
||||
float ascent = 1.0f, heightToPointsFactor = 1.0f;
|
||||
int defaultGlyph = -1, heightInPoints = 0;
|
||||
std::unordered_map<uint64, float> kerningPairs;
|
||||
|
||||
static uint64 kerningPairIndex (int glyph1, int glyph2)
|
||||
{
|
||||
return (((uint64) (uint32) glyph1) << 32) | (uint64) (uint32) glyph2;
|
||||
}
|
||||
|
||||
void loadFont()
|
||||
{
|
||||
SetMapperFlags (dc, 0);
|
||||
SetMapMode (dc, MM_TEXT);
|
||||
|
||||
LOGFONTW lf = {};
|
||||
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));
|
||||
|
||||
auto standardSizedFont = CreateFontIndirect (&lf);
|
||||
|
||||
if (standardSizedFont != nullptr)
|
||||
{
|
||||
if ((previousFontH = SelectObject (dc, standardSizedFont)) != nullptr)
|
||||
{
|
||||
fontH = standardSizedFont;
|
||||
OUTLINETEXTMETRIC otm;
|
||||
|
||||
if (GetOutlineTextMetrics (dc, sizeof (otm), &otm) != 0)
|
||||
{
|
||||
heightInPoints = (int) otm.otmEMSquare;
|
||||
lf.lfHeight = -heightInPoints;
|
||||
fontH = CreateFontIndirect (&lf);
|
||||
|
||||
SelectObject (dc, fontH);
|
||||
DeleteObject (standardSizedFont);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GetTextMetrics (dc, &tm))
|
||||
{
|
||||
auto dpi = (float) (GetDeviceCaps (dc, LOGPIXELSX) + GetDeviceCaps (dc, LOGPIXELSY)) / 2.0f;
|
||||
heightToPointsFactor = (dpi / (float) GetDeviceCaps (dc, LOGPIXELSY)) * (float) heightInPoints / (float) tm.tmHeight;
|
||||
ascent = (float) tm.tmAscent / (float) tm.tmHeight;
|
||||
std::unordered_map<int, int> glyphsForChars;
|
||||
defaultGlyph = getGlyphForChar (dc, glyphsForChars, tm.tmDefaultChar);
|
||||
createKerningPairs (dc, glyphsForChars, (float) tm.tmHeight);
|
||||
}
|
||||
}
|
||||
|
||||
void createKerningPairs (HDC hdc, std::unordered_map<int, int>& glyphsForChars, float height)
|
||||
{
|
||||
HeapBlock<KERNINGPAIR> rawKerning;
|
||||
auto numKPs = GetKerningPairs (hdc, 0, nullptr);
|
||||
rawKerning.calloc (numKPs);
|
||||
GetKerningPairs (hdc, numKPs, rawKerning);
|
||||
|
||||
std::unordered_map<int, int> widthsForGlyphs;
|
||||
|
||||
for (DWORD i = 0; i < numKPs; ++i)
|
||||
{
|
||||
auto glyph1 = getGlyphForChar (hdc, glyphsForChars, rawKerning[i].wFirst);
|
||||
auto glyph2 = getGlyphForChar (hdc, glyphsForChars, rawKerning[i].wSecond);
|
||||
auto standardWidth = getGlyphWidth (hdc, widthsForGlyphs, glyph1);
|
||||
|
||||
kerningPairs[kerningPairIndex (glyph1, glyph2)] = (float) (standardWidth + rawKerning[i].iKernAmount) / height;
|
||||
kerningPairs[kerningPairIndex (glyph1, -1)] = (float) standardWidth / height;
|
||||
}
|
||||
}
|
||||
|
||||
static int getGlyphForChar (HDC dc, std::unordered_map<int, int>& cache, juce_wchar character)
|
||||
{
|
||||
auto existing = cache.find ((int) character);
|
||||
|
||||
if (existing != cache.end())
|
||||
return existing->second;
|
||||
|
||||
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;
|
||||
|
||||
cache[(int) character] = index;
|
||||
return index;
|
||||
}
|
||||
|
||||
static int getGlyphWidth (HDC dc, std::unordered_map<int, int>& cache, int glyphNumber)
|
||||
{
|
||||
auto existing = cache.find (glyphNumber);
|
||||
|
||||
if (existing != cache.end())
|
||||
return existing->second;
|
||||
|
||||
auto width = getGlyphWidth (dc, glyphNumber);
|
||||
cache[glyphNumber] = width;
|
||||
return width;
|
||||
}
|
||||
|
||||
static int getGlyphWidth (HDC dc, int glyphNumber)
|
||||
{
|
||||
GLYPHMETRICS gm;
|
||||
gm.gmCellIncX = 0;
|
||||
GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, nullptr, &identityMatrix);
|
||||
return gm.gmCellIncX;
|
||||
}
|
||||
|
||||
float getKerning (HDC hdc, int glyph1, int glyph2)
|
||||
{
|
||||
auto pair = kerningPairs.find (kerningPairIndex (glyph1, glyph2));
|
||||
|
||||
if (pair != kerningPairs.end())
|
||||
return pair->second;
|
||||
|
||||
auto single = kerningPairs.find (kerningPairIndex (glyph1, -1));
|
||||
|
||||
if (single != kerningPairs.end())
|
||||
return single->second;
|
||||
|
||||
auto width = (float) getGlyphWidth (hdc, glyph1) / (float) tm.tmHeight;
|
||||
kerningPairs[kerningPairIndex (glyph1, -1)] = width;
|
||||
return width;
|
||||
}
|
||||
|
||||
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() && wtf->isFontFound())
|
||||
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
|
29
deps/juce/modules/juce_graphics/native/juce_win32_IconHelpers.cpp
vendored
Normal file
29
deps/juce/modules/juce_graphics/native/juce_win32_IconHelpers.cpp
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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