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:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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 {}; }
}

View 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

View 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

View 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 {}; }
}

View 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

View 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
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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 {}; }
}