fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).

This commit is contained in:
Alex Birch
2018-06-17 13:34:53 +01:00
parent a2be47c887
commit dff4d13a1d
1563 changed files with 601601 additions and 3466 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,401 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct DefaultFontNames
{
DefaultFontNames()
: defaultSans ("sans"),
defaultSerif ("serif"),
defaultFixed ("monospace"),
defaultFallback ("sans")
{
}
String getRealFontName (const String& faceName) const
{
if (faceName == Font::getDefaultSansSerifFontName()) return defaultSans;
if (faceName == Font::getDefaultSerifFontName()) return defaultSerif;
if (faceName == Font::getDefaultMonospacedFontName()) return defaultFixed;
return faceName;
}
String defaultSans, defaultSerif, defaultFixed, defaultFallback;
};
Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
{
static DefaultFontNames defaultNames;
Font f (font);
f.setTypefaceName (defaultNames.getRealFontName (font.getTypefaceName()));
return Typeface::createSystemTypefaceFor (f);
}
//==============================================================================
#if JUCE_USE_FREETYPE
StringArray FTTypefaceList::getDefaultFontDirectories()
{
return StringArray ("/system/fonts");
}
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
{
return new FreeTypeTypeface (font);
}
void Typeface::scanFolderForFonts (const File& folder)
{
FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName()));
}
StringArray Font::findAllTypefaceNames()
{
return FTTypefaceList::getInstance()->findAllFamilyNames();
}
StringArray Font::findAllTypefaceStyles (const String& family)
{
return FTTypefaceList::getInstance()->findAllTypefaceStyles (family);
}
bool TextLayout::createNativeLayout (const AttributedString&)
{
return false;
}
#else
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (create, "create", "(Ljava/lang/String;I)Landroid/graphics/Typeface;") \
STATICMETHOD (createFromFile, "createFromFile", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \
DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface");
#undef JNI_CLASS_MEMBERS
//==============================================================================
StringArray Font::findAllTypefaceNames()
{
StringArray results;
for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, "*.ttf"))
results.addIfNotAlreadyThere (f.getFileNameWithoutExtension().upToLastOccurrenceOf ("-", false, false));
return results;
}
StringArray Font::findAllTypefaceStyles (const String& family)
{
StringArray results ("Regular");
for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, family + "-*.ttf"))
results.addIfNotAlreadyThere (f.getFileNameWithoutExtension().fromLastOccurrenceOf ("-", false, false));
return results;
}
const float referenceFontSize = 256.0f;
const float referenceFontToUnits = 1.0f / referenceFontSize;
//==============================================================================
class AndroidTypeface : public Typeface
{
public:
AndroidTypeface (const Font& font)
: Typeface (font.getTypefaceName(), font.getTypefaceStyle()),
ascent (0), descent (0), heightToPointsFactor (1.0f)
{
JNIEnv* const env = getEnv();
// First check whether there's an embedded asset with this font name:
typeface = GlobalRef (android.activity.callObjectMethod (JuceAppActivity.getTypeFaceFromAsset,
javaString ("fonts/" + name).get()));
if (typeface.get() == nullptr)
{
const bool isBold = style.contains ("Bold");
const bool isItalic = style.contains ("Italic");
File fontFile (getFontFile (name, style));
if (! fontFile.exists())
fontFile = findFontFile (name, isBold, isItalic);
if (fontFile.exists())
typeface = GlobalRef (env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile,
javaString (fontFile.getFullPathName()).get()));
else
typeface = GlobalRef (env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.create,
javaString (getName()).get(),
(isBold ? 1 : 0) + (isItalic ? 2 : 0)));
}
initialise (env);
}
AndroidTypeface (const void* data, size_t size)
: Typeface (String (static_cast<uint64> (reinterpret_cast<uintptr_t> (data))), String())
{
JNIEnv* const env = getEnv();
LocalRef<jbyteArray> bytes (env->NewByteArray ((jsize) size));
env->SetByteArrayRegion (bytes, 0, (jsize) size, (const jbyte*) data);
typeface = GlobalRef (android.activity.callObjectMethod (JuceAppActivity.getTypeFaceFromByteArray, bytes.get()));
initialise (env);
}
void initialise (JNIEnv* const env)
{
rect = GlobalRef (env->NewObject (AndroidRect, AndroidRect.constructor, 0, 0, 0, 0));
paint = GlobalRef (GraphicsHelpers::createPaint (Graphics::highResamplingQuality));
const LocalRef<jobject> ignored (paint.callObjectMethod (AndroidPaint.setTypeface, typeface.get()));
paint.callVoidMethod (AndroidPaint.setTextSize, referenceFontSize);
const float fullAscent = std::abs (paint.callFloatMethod (AndroidPaint.ascent));
const float fullDescent = paint.callFloatMethod (AndroidPaint.descent);
const float totalHeight = fullAscent + fullDescent;
ascent = fullAscent / totalHeight;
descent = fullDescent / totalHeight;
heightToPointsFactor = referenceFontSize / totalHeight;
}
float getAscent() const override { return ascent; }
float getDescent() const override { return descent; }
float getHeightToPointsFactor() const override { return heightToPointsFactor; }
float getStringWidth (const String& text) override
{
JNIEnv* env = getEnv();
const int numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());
jfloatArray widths = env->NewFloatArray (numChars);
const int numDone = paint.callIntMethod (AndroidPaint.getTextWidths, javaString (text).get(), widths);
HeapBlock<jfloat> localWidths (static_cast<size_t> (numDone));
env->GetFloatArrayRegion (widths, 0, numDone, localWidths);
env->DeleteLocalRef (widths);
float x = 0;
for (int i = 0; i < numDone; ++i)
x += localWidths[i];
return x * referenceFontToUnits;
}
void getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) override
{
JNIEnv* env = getEnv();
const int numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());
jfloatArray widths = env->NewFloatArray (numChars);
const int numDone = paint.callIntMethod (AndroidPaint.getTextWidths, javaString (text).get(), widths);
HeapBlock<jfloat> localWidths (static_cast<size_t> (numDone));
env->GetFloatArrayRegion (widths, 0, numDone, localWidths);
env->DeleteLocalRef (widths);
auto s = text.getCharPointer();
xOffsets.add (0);
float x = 0;
for (int i = 0; i < numDone; ++i)
{
const float local = localWidths[i];
// Android uses jchar (UTF-16) characters
jchar ch = (jchar) s.getAndAdvance();
// Android has no proper glyph support, so we have to do
// a hacky workaround for ligature detection
#if JUCE_STRING_UTF_TYPE <= 16
static_assert (sizeof (int) >= (sizeof (jchar) * 2), "Unable store two java chars in one glyph");
// if the width of this glyph is zero inside the string but has
// a width on it's own, then it's probably due to ligature
if (local == 0.0f && glyphs.size() > 0 && getStringWidth (String (ch)) > 0.0f)
{
// modify the previous glyph
int& glyphNumber = glyphs.getReference (glyphs.size() - 1);
// make sure this is not a three character ligature
if (glyphNumber < std::numeric_limits<jchar>::max())
{
const unsigned int previousGlyph
= static_cast<unsigned int> (glyphNumber) & ((1U << (sizeof (jchar) * 8U)) - 1U);
const unsigned int thisGlyph
= static_cast<unsigned int> (ch) & ((1U << (sizeof (jchar) * 8U)) - 1U);
glyphNumber = static_cast<int> ((thisGlyph << (sizeof (jchar) * 8U)) | previousGlyph);
ch = 0;
}
}
#endif
glyphs.add ((int) ch);
x += local;
xOffsets.add (x * referenceFontToUnits);
}
}
bool getOutlineForGlyph (int /*glyphNumber*/, Path& /*destPath*/) override
{
return false;
}
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& t, float /*fontHeight*/) override
{
#if JUCE_STRING_UTF_TYPE <= 16
static_assert (sizeof (int) >= (sizeof (jchar) * 2), "Unable store two jni chars in one int");
// glyphNumber of zero is used to indicate that the last character was a ligature
if (glyphNumber == 0) return nullptr;
jchar ch1 = (static_cast<unsigned int> (glyphNumber) >> 0) & ((1U << (sizeof (jchar) * 8U)) - 1U);
jchar ch2 = (static_cast<unsigned int> (glyphNumber) >> (sizeof (jchar) * 8U)) & ((1U << (sizeof (jchar) * 8U)) - 1U);
#else
jchar ch1 = glyphNumber, ch2 = 0;
#endif
JNIEnv* env = getEnv();
jobject matrix = GraphicsHelpers::createMatrix (env, AffineTransform::scale (referenceFontToUnits).followedBy (t));
jintArray maskData = (jintArray) android.activity.callObjectMethod (JuceAppActivity.renderGlyph, ch1, ch2, paint.get(), matrix, rect.get());
env->DeleteLocalRef (matrix);
const int left = env->GetIntField (rect.get(), AndroidRect.left);
const int top = env->GetIntField (rect.get(), AndroidRect.top);
const int right = env->GetIntField (rect.get(), AndroidRect.right);
const int bottom = env->GetIntField (rect.get(), AndroidRect.bottom);
const Rectangle<int> bounds (left, top, right - left, bottom - top);
EdgeTable* et = nullptr;
if (! bounds.isEmpty())
{
et = new EdgeTable (bounds);
jint* const maskDataElements = env->GetIntArrayElements (maskData, 0);
const jint* mask = maskDataElements;
for (int y = top; y < bottom; ++y)
{
#if JUCE_LITTLE_ENDIAN
const uint8* const lineBytes = ((const uint8*) mask) + 3;
#else
const uint8* const lineBytes = (const uint8*) mask;
#endif
et->clipLineToMask (left, y, lineBytes, 4, bounds.getWidth());
mask += bounds.getWidth();
}
env->ReleaseIntArrayElements (maskData, maskDataElements, 0);
}
env->DeleteLocalRef (maskData);
return et;
}
GlobalRef typeface, paint, rect;
float ascent, descent, heightToPointsFactor;
private:
static File findFontFile (const String& family,
const bool bold, const bool italic)
{
File file;
if (bold || italic)
{
String suffix;
if (bold) suffix = "Bold";
if (italic) suffix << "Italic";
file = getFontFile (family, suffix);
if (file.exists())
return file;
}
file = getFontFile (family, "Regular");
if (! file.exists())
file = getFontFile (family, String());
return file;
}
static File getFontFile (const String& family, const String& style)
{
String path ("/system/fonts/" + family);
if (style.isNotEmpty())
path << '-' << style;
return File (path + ".ttf");
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidTypeface)
};
//==============================================================================
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
{
return new AndroidTypeface (font);
}
Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size)
{
return new AndroidTypeface (data, size);
}
void Typeface::scanFolderForFonts (const File&)
{
jassertfalse; // not available unless using FreeType
}
bool TextLayout::createNativeLayout (const AttributedString&)
{
return false;
}
#endif
} // namespace juce

View File

@ -0,0 +1,67 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace GraphicsHelpers
{
jobject createPaint (Graphics::ResamplingQuality quality)
{
jint constructorFlags = 1 /*ANTI_ALIAS_FLAG*/
| 4 /*DITHER_FLAG*/
| 128 /*SUBPIXEL_TEXT_FLAG*/;
if (quality > Graphics::lowResamplingQuality)
constructorFlags |= 2; /*FILTER_BITMAP_FLAG*/
return getEnv()->NewObject (AndroidPaint, AndroidPaint.constructor, constructorFlags);
}
const jobject createMatrix (JNIEnv* env, const AffineTransform& t)
{
jobject m = env->NewObject (AndroidMatrix, AndroidMatrix.constructor);
jfloat values[9] = { t.mat00, t.mat01, t.mat02,
t.mat10, t.mat11, t.mat12,
0.0f, 0.0f, 1.0f };
jfloatArray javaArray = env->NewFloatArray (9);
env->SetFloatArrayRegion (javaArray, 0, 9, values);
env->CallVoidMethod (m, AndroidMatrix.setValues, javaArray);
env->DeleteLocalRef (javaArray);
return m;
}
}
ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const
{
return SoftwareImageType().create (format, width, height, clearImage);
}
} // namespace juce

View File

@ -0,0 +1,30 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
Image JUCE_API getIconFromApplication (const String&, int) { return {}; }
}

View File

@ -0,0 +1,462 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct FTLibWrapper : public ReferenceCountedObject
{
FTLibWrapper() : library (0)
{
if (FT_Init_FreeType (&library) != 0)
{
library = 0;
DBG ("Failed to initialize FreeType");
}
}
~FTLibWrapper()
{
if (library != 0)
FT_Done_FreeType (library);
}
FT_Library library;
using Ptr = ReferenceCountedObjectPtr<FTLibWrapper>;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTLibWrapper)
};
//==============================================================================
struct FTFaceWrapper : public ReferenceCountedObject
{
FTFaceWrapper (const FTLibWrapper::Ptr& ftLib, const File& file, int faceIndex)
: face (0), library (ftLib)
{
if (FT_New_Face (ftLib->library, file.getFullPathName().toUTF8(), faceIndex, &face) != 0)
face = 0;
}
FTFaceWrapper (const FTLibWrapper::Ptr& ftLib, const void* data, size_t dataSize, int faceIndex)
: face (0), library (ftLib), savedFaceData (data, dataSize)
{
if (FT_New_Memory_Face (ftLib->library, (const FT_Byte*) savedFaceData.getData(),
(FT_Long) savedFaceData.getSize(), faceIndex, &face) != 0)
face = 0;
}
~FTFaceWrapper()
{
if (face != 0)
FT_Done_Face (face);
}
FT_Face face;
FTLibWrapper::Ptr library;
MemoryBlock savedFaceData;
using Ptr = ReferenceCountedObjectPtr<FTFaceWrapper>;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTFaceWrapper)
};
//==============================================================================
class FTTypefaceList : private DeletedAtShutdown
{
public:
FTTypefaceList() : library (new FTLibWrapper())
{
scanFontPaths (getDefaultFontDirectories());
}
~FTTypefaceList()
{
clearSingletonInstance();
}
//==============================================================================
struct KnownTypeface
{
KnownTypeface (const File& f, const int index, const FTFaceWrapper& face)
: file (f),
family (face.face->family_name),
style (face.face->style_name),
faceIndex (index),
isMonospaced ((face.face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) != 0),
isSansSerif (isFaceSansSerif (family))
{
}
const File file;
const String family, style;
const int faceIndex;
const bool isMonospaced, isSansSerif;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownTypeface)
};
//==============================================================================
static FTFaceWrapper::Ptr selectUnicodeCharmap (FTFaceWrapper* face)
{
if (face != nullptr)
if (FT_Select_Charmap (face->face, ft_encoding_unicode) != 0)
FT_Set_Charmap (face->face, face->face->charmaps[0]);
return face;
}
FTFaceWrapper::Ptr createFace (const void* data, size_t dataSize, int index)
{
return selectUnicodeCharmap (new FTFaceWrapper (library, data, dataSize, index));
}
FTFaceWrapper::Ptr createFace (const File& file, int index)
{
return selectUnicodeCharmap (new FTFaceWrapper (library, file, index));
}
FTFaceWrapper::Ptr createFace (const String& fontName, const String& fontStyle)
{
const KnownTypeface* ftFace = matchTypeface (fontName, fontStyle);
if (ftFace == nullptr) ftFace = matchTypeface (fontName, "Regular");
if (ftFace == nullptr) ftFace = matchTypeface (fontName, String());
if (ftFace != nullptr)
return createFace (ftFace->file, ftFace->faceIndex);
return nullptr;
}
//==============================================================================
StringArray findAllFamilyNames() const
{
StringArray s;
for (int i = 0; i < faces.size(); ++i)
s.addIfNotAlreadyThere (faces.getUnchecked(i)->family);
return s;
}
static int indexOfRegularStyle (const StringArray& styles)
{
int i = styles.indexOf ("Regular", true);
if (i < 0)
for (i = 0; i < styles.size(); ++i)
if (! (styles[i].containsIgnoreCase ("Bold") || styles[i].containsIgnoreCase ("Italic")))
break;
return i;
}
StringArray findAllTypefaceStyles (const String& family) const
{
StringArray s;
for (int i = 0; i < faces.size(); ++i)
{
const KnownTypeface* const face = faces.getUnchecked(i);
if (face->family == family)
s.addIfNotAlreadyThere (face->style);
}
// try to get a regular style to be first in the list
const int regular = indexOfRegularStyle (s);
if (regular > 0)
s.strings.swap (0, regular);
return s;
}
void scanFontPaths (const StringArray& paths)
{
for (int i = 0; i < paths.size(); ++i)
{
DirectoryIterator iter (File::getCurrentWorkingDirectory()
.getChildFile (paths[i]), true);
while (iter.next())
if (iter.getFile().hasFileExtension ("ttf;pfb;pcf;otf"))
scanFont (iter.getFile());
}
}
void getMonospacedNames (StringArray& monoSpaced) const
{
for (int i = 0; i < faces.size(); ++i)
if (faces.getUnchecked(i)->isMonospaced)
monoSpaced.addIfNotAlreadyThere (faces.getUnchecked(i)->family);
}
void getSerifNames (StringArray& serif) const
{
for (int i = 0; i < faces.size(); ++i)
if (! faces.getUnchecked(i)->isSansSerif)
serif.addIfNotAlreadyThere (faces.getUnchecked(i)->family);
}
void getSansSerifNames (StringArray& sansSerif) const
{
for (int i = 0; i < faces.size(); ++i)
if (faces.getUnchecked(i)->isSansSerif)
sansSerif.addIfNotAlreadyThere (faces.getUnchecked(i)->family);
}
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (FTTypefaceList)
private:
FTLibWrapper::Ptr library;
OwnedArray<KnownTypeface> faces;
static StringArray getDefaultFontDirectories();
void scanFont (const File& file)
{
int faceIndex = 0;
int numFaces = 0;
do
{
FTFaceWrapper face (library, file, faceIndex);
if (face.face != 0)
{
if (faceIndex == 0)
numFaces = (int) face.face->num_faces;
if ((face.face->face_flags & FT_FACE_FLAG_SCALABLE) != 0)
faces.add (new KnownTypeface (file, faceIndex, face));
}
++faceIndex;
}
while (faceIndex < numFaces);
}
const KnownTypeface* matchTypeface (const String& familyName, const String& style) const noexcept
{
for (int i = 0; i < faces.size(); ++i)
{
const KnownTypeface* const face = faces.getUnchecked(i);
if (face->family == familyName
&& (face->style.equalsIgnoreCase (style) || style.isEmpty()))
return face;
}
return nullptr;
}
static bool isFaceSansSerif (const String& family)
{
static const char* sansNames[] = { "Sans", "Verdana", "Arial", "Ubuntu" };
for (int i = 0; i < numElementsInArray (sansNames); ++i)
if (family.containsIgnoreCase (sansNames[i]))
return true;
return false;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTTypefaceList)
};
JUCE_IMPLEMENT_SINGLETON (FTTypefaceList)
//==============================================================================
class FreeTypeTypeface : public CustomTypeface
{
public:
FreeTypeTypeface (const Font& font)
: faceWrapper (FTTypefaceList::getInstance()->createFace (font.getTypefaceName(),
font.getTypefaceStyle()))
{
if (faceWrapper != nullptr)
initialiseCharacteristics (font.getTypefaceName(),
font.getTypefaceStyle());
}
FreeTypeTypeface (const void* data, size_t dataSize)
: faceWrapper (FTTypefaceList::getInstance()->createFace (data, dataSize, 0))
{
if (faceWrapper != nullptr)
initialiseCharacteristics (faceWrapper->face->family_name,
faceWrapper->face->style_name);
}
void initialiseCharacteristics (const String& fontName, const String& fontStyle)
{
setCharacteristics (fontName, fontStyle,
faceWrapper->face->ascender / (float) (faceWrapper->face->ascender - faceWrapper->face->descender),
L' ');
}
bool loadGlyphIfPossible (const juce_wchar character)
{
if (faceWrapper != nullptr)
{
FT_Face face = faceWrapper->face;
const unsigned int glyphIndex = FT_Get_Char_Index (face, (FT_ULong) character);
if (FT_Load_Glyph (face, glyphIndex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM | FT_LOAD_NO_HINTING) == 0
&& face->glyph->format == ft_glyph_format_outline)
{
const float scale = 1.0f / (float) (face->ascender - face->descender);
Path destShape;
if (getGlyphShape (destShape, face->glyph->outline, scale))
{
addGlyph (character, destShape, face->glyph->metrics.horiAdvance * scale);
if ((face->face_flags & FT_FACE_FLAG_KERNING) != 0)
addKerning (face, (uint32) character, glyphIndex);
return true;
}
}
}
return false;
}
private:
FTFaceWrapper::Ptr faceWrapper;
bool getGlyphShape (Path& destShape, const FT_Outline& outline, const float scaleX)
{
const float scaleY = -scaleX;
const short* const contours = outline.contours;
const char* const tags = outline.tags;
const FT_Vector* const points = outline.points;
for (int c = 0; c < outline.n_contours; ++c)
{
const int startPoint = (c == 0) ? 0 : contours [c - 1] + 1;
const int endPoint = contours[c];
for (int p = startPoint; p <= endPoint; ++p)
{
const float x = scaleX * points[p].x;
const float y = scaleY * points[p].y;
if (p == startPoint)
{
if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Conic)
{
float x2 = scaleX * points [endPoint].x;
float y2 = scaleY * points [endPoint].y;
if (FT_CURVE_TAG (tags[endPoint]) != FT_Curve_Tag_On)
{
x2 = (x + x2) * 0.5f;
y2 = (y + y2) * 0.5f;
}
destShape.startNewSubPath (x2, y2);
}
else
{
destShape.startNewSubPath (x, y);
}
}
if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_On)
{
if (p != startPoint)
destShape.lineTo (x, y);
}
else if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Conic)
{
const int nextIndex = (p == endPoint) ? startPoint : p + 1;
float x2 = scaleX * points [nextIndex].x;
float y2 = scaleY * points [nextIndex].y;
if (FT_CURVE_TAG (tags [nextIndex]) == FT_Curve_Tag_Conic)
{
x2 = (x + x2) * 0.5f;
y2 = (y + y2) * 0.5f;
}
else
{
++p;
}
destShape.quadraticTo (x, y, x2, y2);
}
else if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Cubic)
{
const int next1 = p + 1;
const int next2 = (p == (endPoint - 1)) ? startPoint : (p + 2);
if (p >= endPoint
|| FT_CURVE_TAG (tags[next1]) != FT_Curve_Tag_Cubic
|| FT_CURVE_TAG (tags[next2]) != FT_Curve_Tag_On)
return false;
const float x2 = scaleX * points [next1].x;
const float y2 = scaleY * points [next1].y;
const float x3 = scaleX * points [next2].x;
const float y3 = scaleY * points [next2].y;
destShape.cubicTo (x, y, x2, y2, x3, y3);
p += 2;
}
}
destShape.closeSubPath();
}
return true;
}
void addKerning (FT_Face face, const uint32 character, const uint32 glyphIndex)
{
const float height = (float) (face->ascender - face->descender);
uint32 rightGlyphIndex;
FT_ULong rightCharCode = FT_Get_First_Char (face, &rightGlyphIndex);
while (rightGlyphIndex != 0)
{
FT_Vector kerning;
if (FT_Get_Kerning (face, glyphIndex, rightGlyphIndex, ft_kerning_unscaled, &kerning) == 0
&& kerning.x != 0)
addKerningPair ((juce_wchar) character, (juce_wchar) rightCharCode, kerning.x / height);
rightCharCode = FT_Get_Next_Char (face, rightCharCode, &rightGlyphIndex);
}
}
JUCE_DECLARE_NON_COPYABLE (FreeTypeTypeface)
};
} // namespace juce

View File

@ -0,0 +1,199 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
static XmlElement* findFontsConfFile()
{
static const char* pathsToSearch[] = { "/etc/fonts/fonts.conf",
"/usr/share/fonts/fonts.conf" };
for (auto* path : pathsToSearch)
if (auto* xml = XmlDocument::parse (File (path)))
return xml;
return nullptr;
}
StringArray FTTypefaceList::getDefaultFontDirectories()
{
StringArray fontDirs;
fontDirs.addTokens (String (CharPointer_UTF8 (getenv ("JUCE_FONT_PATH"))), ";,", "");
fontDirs.removeEmptyStrings (true);
if (fontDirs.isEmpty())
{
std::unique_ptr<XmlElement> fontsInfo (findFontsConfFile());
if (fontsInfo != nullptr)
{
forEachXmlChildElementWithTagName (*fontsInfo, e, "dir")
{
auto fontPath = e->getAllSubText().trim();
if (fontPath.isNotEmpty())
{
if (e->getStringAttribute ("prefix") == "xdg")
{
auto xdgDataHome = SystemStats::getEnvironmentVariable ("XDG_DATA_HOME", {});
if (xdgDataHome.trimStart().isEmpty())
xdgDataHome = "~/.local/share";
fontPath = File (xdgDataHome).getChildFile (fontPath).getFullPathName();
}
fontDirs.add (fontPath);
}
}
}
}
if (fontDirs.isEmpty())
fontDirs.add ("/usr/X11R6/lib/X11/fonts");
fontDirs.removeDuplicates (false);
return fontDirs;
}
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
{
return new FreeTypeTypeface (font);
}
Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize)
{
return new FreeTypeTypeface (data, dataSize);
}
void Typeface::scanFolderForFonts (const File& folder)
{
FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName()));
}
StringArray Font::findAllTypefaceNames()
{
return FTTypefaceList::getInstance()->findAllFamilyNames();
}
StringArray Font::findAllTypefaceStyles (const String& family)
{
return FTTypefaceList::getInstance()->findAllTypefaceStyles (family);
}
bool TextLayout::createNativeLayout (const AttributedString&)
{
return false;
}
//==============================================================================
struct DefaultFontNames
{
DefaultFontNames()
: defaultSans (getDefaultSansSerifFontName()),
defaultSerif (getDefaultSerifFontName()),
defaultFixed (getDefaultMonospacedFontName())
{
}
String getRealFontName (const String& faceName) const
{
if (faceName == Font::getDefaultSansSerifFontName()) return defaultSans;
if (faceName == Font::getDefaultSerifFontName()) return defaultSerif;
if (faceName == Font::getDefaultMonospacedFontName()) return defaultFixed;
return faceName;
}
String defaultSans, defaultSerif, defaultFixed;
private:
static String pickBestFont (const StringArray& names, const char* const* choicesArray)
{
const StringArray choices (choicesArray);
for (auto& choice : choices)
if (names.contains (choice, true))
return choice;
for (auto& choice : choices)
for (auto& name : names)
if (name.startsWithIgnoreCase (choice))
return name;
for (auto& choice : choices)
for (auto& name : names)
if (name.containsIgnoreCase (choice))
return name;
return names[0];
}
static String getDefaultSansSerifFontName()
{
StringArray allFonts;
FTTypefaceList::getInstance()->getSansSerifNames (allFonts);
static const char* targets[] = { "Verdana", "Bitstream Vera Sans", "Luxi Sans",
"Liberation Sans", "DejaVu Sans", "Sans", nullptr };
return pickBestFont (allFonts, targets);
}
static String getDefaultSerifFontName()
{
StringArray allFonts;
FTTypefaceList::getInstance()->getSerifNames (allFonts);
static const char* targets[] = { "Bitstream Vera Serif", "Times", "Nimbus Roman",
"Liberation Serif", "DejaVu Serif", "Serif", nullptr };
return pickBestFont (allFonts, targets);
}
static String getDefaultMonospacedFontName()
{
StringArray allFonts;
FTTypefaceList::getInstance()->getMonospacedNames (allFonts);
static const char* targets[] = { "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Sans Mono",
"Liberation Mono", "Courier", "DejaVu Mono", "Mono", nullptr };
return pickBestFont (allFonts, targets);
}
JUCE_DECLARE_NON_COPYABLE (DefaultFontNames)
};
Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
{
static DefaultFontNames defaultNames;
Font f (font);
f.setTypefaceName (defaultNames.getRealFontName (font.getTypefaceName()));
return Typeface::createSystemTypefaceFor (f);
}
} // namespace juce

View File

@ -0,0 +1,30 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
Image JUCE_API getIconFromApplication (const String&, int) { return {}; }
}

View File

@ -0,0 +1,115 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class CoreGraphicsContext : public LowLevelGraphicsContext
{
public:
CoreGraphicsContext (CGContextRef context, float flipHeight, float targetScale);
~CoreGraphicsContext();
//==============================================================================
bool isVectorDevice() const override { return false; }
void setOrigin (Point<int>) override;
void addTransform (const AffineTransform&) override;
float getPhysicalPixelScaleFactor() override;
bool clipToRectangle (const Rectangle<int>&) override;
bool clipToRectangleList (const RectangleList<int>&) override;
void excludeClipRectangle (const Rectangle<int>&) override;
void clipToPath (const Path&, const AffineTransform&) override;
void clipToImageAlpha (const Image&, const AffineTransform&) override;
bool clipRegionIntersects (const Rectangle<int>&) override;
Rectangle<int> getClipBounds() const override;
bool isClipEmpty() const override;
//==============================================================================
void saveState() override;
void restoreState() override;
void beginTransparencyLayer (float opacity) override;
void endTransparencyLayer() override;
//==============================================================================
void setFill (const FillType&) override;
void setOpacity (float) override;
void setInterpolationQuality (Graphics::ResamplingQuality) override;
//==============================================================================
void fillRect (const Rectangle<int>&, bool replaceExistingContents) override;
void fillRect (const Rectangle<float>&) override;
void fillRectList (const RectangleList<float>&) override;
void fillPath (const Path&, const AffineTransform&) override;
void drawImage (const Image& sourceImage, const AffineTransform&) override;
//==============================================================================
void drawLine (const Line<float>&) override;
void setFont (const Font&) override;
const Font& getFont() override;
void drawGlyph (int glyphNumber, const AffineTransform&) override;
bool drawTextLayout (const AttributedString&, const Rectangle<float>&) override;
private:
CGContextRef context;
const CGFloat flipHeight;
float targetScale;
CGColorSpaceRef rgbColourSpace, greyColourSpace;
mutable Rectangle<int> lastClipRect;
mutable bool lastClipRectIsValid;
struct SavedState
{
SavedState();
SavedState (const SavedState&);
~SavedState();
void setFill (const FillType&);
FillType fillType;
Font font;
CGFontRef fontRef;
CGAffineTransform fontTransform;
CGGradientRef gradient;
};
std::unique_ptr<SavedState> state;
OwnedArray<SavedState> stateStack;
void drawGradient();
void createPath (const Path&) const;
void createPath (const Path&, const AffineTransform&) const;
void flip() const;
void applyTransform (const AffineTransform&) const;
void drawImage (const Image&, const AffineTransform&, bool fillEntireClipAsTiles);
bool clipToRectangleListWithoutTest (const RectangleList<int>&);
void fillCGRect (const CGRect&, bool replaceExistingContents);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsContext)
};
} // namespace juce

View File

@ -0,0 +1,946 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class CoreGraphicsImage : public ImagePixelData
{
public:
CoreGraphicsImage (const Image::PixelFormat format, const int w, const int h, const bool clearImage)
: ImagePixelData (format, w, h), cachedImageRef (0)
{
pixelStride = format == Image::RGB ? 3 : ((format == Image::ARGB) ? 4 : 1);
lineStride = (pixelStride * jmax (1, width) + 3) & ~3;
imageData.allocate ((size_t) lineStride * (size_t) jmax (1, height), clearImage);
CGColorSpaceRef colourSpace = (format == Image::SingleChannel) ? CGColorSpaceCreateDeviceGray()
: CGColorSpaceCreateDeviceRGB();
context = CGBitmapContextCreate (imageData, (size_t) width, (size_t) height, 8, (size_t) lineStride,
colourSpace, getCGImageFlags (format));
CGColorSpaceRelease (colourSpace);
}
~CoreGraphicsImage()
{
freeCachedImageRef();
CGContextRelease (context);
}
LowLevelGraphicsContext* createLowLevelContext() override
{
freeCachedImageRef();
sendDataChangeMessage();
return new CoreGraphicsContext (context, height, 1.0f);
}
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override
{
bitmap.data = imageData + x * pixelStride + y * lineStride;
bitmap.pixelFormat = pixelFormat;
bitmap.lineStride = lineStride;
bitmap.pixelStride = pixelStride;
if (mode != Image::BitmapData::readOnly)
{
freeCachedImageRef();
sendDataChangeMessage();
}
}
ImagePixelData::Ptr clone() override
{
CoreGraphicsImage* im = new CoreGraphicsImage (pixelFormat, width, height, false);
memcpy (im->imageData, imageData, (size_t) (lineStride * height));
return im;
}
ImageType* createType() const override { return new NativeImageType(); }
//==============================================================================
static CGImageRef getCachedImageRef (const Image& juceImage, CGColorSpaceRef colourSpace)
{
CoreGraphicsImage* const cgim = dynamic_cast<CoreGraphicsImage*> (juceImage.getPixelData());
if (cgim != nullptr && cgim->cachedImageRef != 0)
{
CGImageRetain (cgim->cachedImageRef);
return cgim->cachedImageRef;
}
CGImageRef ref = createImage (juceImage, colourSpace, false);
if (cgim != nullptr)
{
CGImageRetain (ref);
cgim->cachedImageRef = ref;
}
return ref;
}
static CGImageRef createImage (const Image& juceImage, CGColorSpaceRef colourSpace, const bool mustOutliveSource)
{
const Image::BitmapData srcData (juceImage, Image::BitmapData::readOnly);
CGDataProviderRef provider;
if (mustOutliveSource)
{
CFDataRef data = CFDataCreate (0, (const UInt8*) srcData.data, (CFIndex) ((size_t) srcData.lineStride * (size_t) srcData.height));
provider = CGDataProviderCreateWithCFData (data);
CFRelease (data);
}
else
{
provider = CGDataProviderCreateWithData (0, srcData.data, (size_t) srcData.lineStride * (size_t) srcData.height, 0);
}
CGImageRef imageRef = CGImageCreate ((size_t) srcData.width,
(size_t) srcData.height,
8, (size_t) srcData.pixelStride * 8,
(size_t) srcData.lineStride,
colourSpace, getCGImageFlags (juceImage.getFormat()), provider,
0, true, kCGRenderingIntentDefault);
CGDataProviderRelease (provider);
return imageRef;
}
//==============================================================================
CGContextRef context;
CGImageRef cachedImageRef;
HeapBlock<uint8> imageData;
int pixelStride, lineStride;
private:
void freeCachedImageRef()
{
if (cachedImageRef != 0)
{
CGImageRelease (cachedImageRef);
cachedImageRef = 0;
}
}
static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
{
#if JUCE_BIG_ENDIAN
return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big) : kCGBitmapByteOrderDefault;
#else
return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) : kCGBitmapByteOrderDefault;
#endif
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsImage)
};
ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const
{
return new CoreGraphicsImage (format == Image::RGB ? Image::ARGB : format, width, height, clearImage);
}
//==============================================================================
CoreGraphicsContext::CoreGraphicsContext (CGContextRef c, const float h, const float scale)
: context (c),
flipHeight (h),
targetScale (scale),
lastClipRectIsValid (false),
state (new SavedState())
{
CGContextRetain (context);
CGContextSaveGState (context);
CGContextSetShouldSmoothFonts (context, true);
CGContextSetAllowsFontSmoothing (context, true);
CGContextSetShouldAntialias (context, true);
CGContextSetBlendMode (context, kCGBlendModeNormal);
rgbColourSpace = CGColorSpaceCreateDeviceRGB();
greyColourSpace = CGColorSpaceCreateDeviceGray();
setFont (Font());
}
CoreGraphicsContext::~CoreGraphicsContext()
{
CGContextRestoreGState (context);
CGContextRelease (context);
CGColorSpaceRelease (rgbColourSpace);
CGColorSpaceRelease (greyColourSpace);
}
//==============================================================================
void CoreGraphicsContext::setOrigin (Point<int> o)
{
CGContextTranslateCTM (context, o.x, -o.y);
if (lastClipRectIsValid)
lastClipRect.translate (-o.x, -o.y);
}
void CoreGraphicsContext::addTransform (const AffineTransform& transform)
{
applyTransform (AffineTransform::verticalFlip ((float) flipHeight)
.followedBy (transform)
.translated (0, (float) -flipHeight)
.scaled (1.0f, -1.0f));
lastClipRectIsValid = false;
jassert (getPhysicalPixelScaleFactor() > 0.0f);
jassert (getPhysicalPixelScaleFactor() > 0.0f);
}
float CoreGraphicsContext::getPhysicalPixelScaleFactor()
{
const CGAffineTransform t = CGContextGetCTM (context);
return targetScale * (float) (juce_hypot (t.a, t.c) + juce_hypot (t.b, t.d)) / 2.0f;
}
bool CoreGraphicsContext::clipToRectangle (const Rectangle<int>& r)
{
CGContextClipToRect (context, CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()));
if (lastClipRectIsValid)
{
// This is actually incorrect, because the actual clip region may be complex, and
// clipping its bounds to a rect may not be right... But, removing this shortcut
// doesn't actually fix anything because CoreGraphics also ignores complex regions
// when calculating the resultant clip bounds, and makes the same mistake!
lastClipRect = lastClipRect.getIntersection (r);
return ! lastClipRect.isEmpty();
}
return ! isClipEmpty();
}
bool CoreGraphicsContext::clipToRectangleListWithoutTest (const RectangleList<int>& clipRegion)
{
if (clipRegion.isEmpty())
{
CGContextClipToRect (context, CGRectZero);
lastClipRectIsValid = true;
lastClipRect = Rectangle<int>();
return false;
}
auto numRects = (size_t) clipRegion.getNumRectangles();
HeapBlock<CGRect> rects (numRects);
int i = 0;
for (auto& r : clipRegion)
rects[i++] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight());
CGContextClipToRects (context, rects, numRects);
lastClipRectIsValid = false;
return true;
}
bool CoreGraphicsContext::clipToRectangleList (const RectangleList<int>& clipRegion)
{
return clipToRectangleListWithoutTest (clipRegion) && ! isClipEmpty();
}
void CoreGraphicsContext::excludeClipRectangle (const Rectangle<int>& r)
{
RectangleList<int> remaining (getClipBounds());
remaining.subtract (r);
clipToRectangleListWithoutTest (remaining);
}
void CoreGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform)
{
createPath (path, transform);
if (path.isUsingNonZeroWinding())
CGContextClip (context);
else
CGContextEOClip (context);
lastClipRectIsValid = false;
}
void CoreGraphicsContext::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform)
{
if (! transform.isSingularity())
{
Image singleChannelImage (sourceImage);
if (sourceImage.getFormat() != Image::SingleChannel)
singleChannelImage = sourceImage.convertedToFormat (Image::SingleChannel);
CGImageRef image = CoreGraphicsImage::createImage (singleChannelImage, greyColourSpace, true);
flip();
AffineTransform t (AffineTransform::verticalFlip (sourceImage.getHeight()).followedBy (transform));
applyTransform (t);
CGRect r = convertToCGRect (sourceImage.getBounds());
CGContextClipToMask (context, r, image);
applyTransform (t.inverted());
flip();
CGImageRelease (image);
lastClipRectIsValid = false;
}
}
bool CoreGraphicsContext::clipRegionIntersects (const Rectangle<int>& r)
{
return getClipBounds().intersects (r);
}
Rectangle<int> CoreGraphicsContext::getClipBounds() const
{
if (! lastClipRectIsValid)
{
CGRect bounds = CGRectIntegral (CGContextGetClipBoundingBox (context));
lastClipRectIsValid = true;
lastClipRect.setBounds (roundToInt (bounds.origin.x),
roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)),
roundToInt (bounds.size.width),
roundToInt (bounds.size.height));
}
return lastClipRect;
}
bool CoreGraphicsContext::isClipEmpty() const
{
return getClipBounds().isEmpty();
}
//==============================================================================
void CoreGraphicsContext::saveState()
{
CGContextSaveGState (context);
stateStack.add (new SavedState (*state));
}
void CoreGraphicsContext::restoreState()
{
CGContextRestoreGState (context);
if (auto* top = stateStack.getLast())
{
state.reset (top);
stateStack.removeLast (1, false);
lastClipRectIsValid = false;
}
else
{
jassertfalse; // trying to pop with an empty stack!
}
}
void CoreGraphicsContext::beginTransparencyLayer (float opacity)
{
saveState();
CGContextSetAlpha (context, opacity);
CGContextBeginTransparencyLayer (context, 0);
}
void CoreGraphicsContext::endTransparencyLayer()
{
CGContextEndTransparencyLayer (context);
restoreState();
}
//==============================================================================
void CoreGraphicsContext::setFill (const FillType& fillType)
{
state->setFill (fillType);
if (fillType.isColour())
{
CGContextSetRGBFillColor (context, fillType.colour.getFloatRed(), fillType.colour.getFloatGreen(),
fillType.colour.getFloatBlue(), fillType.colour.getFloatAlpha());
CGContextSetAlpha (context, 1.0f);
}
}
void CoreGraphicsContext::setOpacity (float newOpacity)
{
state->fillType.setOpacity (newOpacity);
setFill (state->fillType);
}
void CoreGraphicsContext::setInterpolationQuality (Graphics::ResamplingQuality quality)
{
switch (quality)
{
case Graphics::lowResamplingQuality: CGContextSetInterpolationQuality (context, kCGInterpolationNone); return;
case Graphics::mediumResamplingQuality: CGContextSetInterpolationQuality (context, kCGInterpolationMedium); return;
case Graphics::highResamplingQuality: CGContextSetInterpolationQuality (context, kCGInterpolationHigh); return;
default: return;
}
}
//==============================================================================
void CoreGraphicsContext::fillRect (const Rectangle<int>& r, const bool replaceExistingContents)
{
fillCGRect (CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()), replaceExistingContents);
}
void CoreGraphicsContext::fillRect (const Rectangle<float>& r)
{
fillCGRect (CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()), false);
}
void CoreGraphicsContext::fillCGRect (const CGRect& cgRect, const bool replaceExistingContents)
{
if (replaceExistingContents)
{
CGContextSetBlendMode (context, kCGBlendModeCopy);
fillCGRect (cgRect, false);
CGContextSetBlendMode (context, kCGBlendModeNormal);
}
else
{
if (state->fillType.isColour())
{
CGContextFillRect (context, cgRect);
}
else if (state->fillType.isGradient())
{
CGContextSaveGState (context);
CGContextClipToRect (context, cgRect);
drawGradient();
CGContextRestoreGState (context);
}
else
{
CGContextSaveGState (context);
CGContextClipToRect (context, cgRect);
drawImage (state->fillType.image, state->fillType.transform, true);
CGContextRestoreGState (context);
}
}
}
void CoreGraphicsContext::fillPath (const Path& path, const AffineTransform& transform)
{
CGContextSaveGState (context);
if (state->fillType.isColour())
{
flip();
applyTransform (transform);
createPath (path);
if (path.isUsingNonZeroWinding())
CGContextFillPath (context);
else
CGContextEOFillPath (context);
}
else
{
createPath (path, transform);
if (path.isUsingNonZeroWinding())
CGContextClip (context);
else
CGContextEOClip (context);
if (state->fillType.isGradient())
drawGradient();
else
drawImage (state->fillType.image, state->fillType.transform, true);
}
CGContextRestoreGState (context);
}
void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform)
{
drawImage (sourceImage, transform, false);
}
void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform, const bool fillEntireClipAsTiles)
{
const int iw = sourceImage.getWidth();
const int ih = sourceImage.getHeight();
CGImageRef image = CoreGraphicsImage::getCachedImageRef (sourceImage, sourceImage.getFormat() == Image::PixelFormat::SingleChannel ? greyColourSpace
: rgbColourSpace);
CGContextSaveGState (context);
CGContextSetAlpha (context, state->fillType.getOpacity());
flip();
applyTransform (AffineTransform::verticalFlip (ih).followedBy (transform));
CGRect imageRect = CGRectMake (0, 0, iw, ih);
if (fillEntireClipAsTiles)
{
#if JUCE_IOS
CGContextDrawTiledImage (context, imageRect, image);
#else
// There's a bug in CGContextDrawTiledImage that makes it incredibly slow
// if it's doing a transformation - it's quicker to just draw lots of images manually
if (&CGContextDrawTiledImage != 0 && transform.isOnlyTranslation())
{
CGContextDrawTiledImage (context, imageRect, image);
}
else
{
// Fallback to manually doing a tiled fill
CGRect clip = CGRectIntegral (CGContextGetClipBoundingBox (context));
int x = 0, y = 0;
while (x > clip.origin.x) x -= iw;
while (y > clip.origin.y) y -= ih;
const int right = (int) (clip.origin.x + clip.size.width);
const int bottom = (int) (clip.origin.y + clip.size.height);
while (y < bottom)
{
for (int x2 = x; x2 < right; x2 += iw)
CGContextDrawImage (context, CGRectMake (x2, y, iw, ih), image);
y += ih;
}
}
#endif
}
else
{
CGContextDrawImage (context, imageRect, image);
}
CGImageRelease (image); // (This causes a memory bug in iOS sim 3.0 - try upgrading to a later version if you hit this)
CGContextRestoreGState (context);
}
//==============================================================================
void CoreGraphicsContext::drawLine (const Line<float>& line)
{
if (state->fillType.isColour())
{
CGContextSetLineCap (context, kCGLineCapSquare);
CGContextSetLineWidth (context, 1.0f);
CGContextSetRGBStrokeColor (context,
state->fillType.colour.getFloatRed(), state->fillType.colour.getFloatGreen(),
state->fillType.colour.getFloatBlue(), state->fillType.colour.getFloatAlpha());
CGPoint cgLine[] = { { (CGFloat) line.getStartX(), flipHeight - (CGFloat) line.getStartY() },
{ (CGFloat) line.getEndX(), flipHeight - (CGFloat) line.getEndY() } };
CGContextStrokeLineSegments (context, cgLine, 1);
}
else
{
Path p;
p.addLineSegment (line, 1.0f);
fillPath (p, AffineTransform());
}
}
void CoreGraphicsContext::fillRectList (const RectangleList<float>& list)
{
HeapBlock<CGRect> rects (list.getNumRectangles());
size_t num = 0;
for (auto& r : list)
rects[num++] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight());
if (state->fillType.isColour())
{
CGContextFillRects (context, rects, num);
}
else if (state->fillType.isGradient())
{
CGContextSaveGState (context);
CGContextClipToRects (context, rects, num);
drawGradient();
CGContextRestoreGState (context);
}
else
{
CGContextSaveGState (context);
CGContextClipToRects (context, rects, num);
drawImage (state->fillType.image, state->fillType.transform, true);
CGContextRestoreGState (context);
}
}
void CoreGraphicsContext::setFont (const Font& newFont)
{
if (state->font != newFont)
{
state->fontRef = 0;
state->font = newFont;
if (OSXTypeface* osxTypeface = dynamic_cast<OSXTypeface*> (state->font.getTypeface()))
{
state->fontRef = osxTypeface->fontRef;
CGContextSetFont (context, state->fontRef);
CGContextSetFontSize (context, state->font.getHeight() * osxTypeface->fontHeightToPointsFactor);
state->fontTransform = osxTypeface->renderingTransform;
state->fontTransform.a *= state->font.getHorizontalScale();
CGContextSetTextMatrix (context, state->fontTransform);
}
}
}
const Font& CoreGraphicsContext::getFont()
{
return state->font;
}
void CoreGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& transform)
{
if (state->fontRef != 0 && state->fillType.isColour())
{
#if JUCE_CLANG
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
if (transform.isOnlyTranslation())
{
CGContextSetTextMatrix (context, state->fontTransform); // have to set this each time, as it's not saved as part of the state
CGGlyph g = (CGGlyph) glyphNumber;
CGContextShowGlyphsAtPoint (context, transform.getTranslationX(),
flipHeight - roundToInt (transform.getTranslationY()), &g, 1);
}
else
{
CGContextSaveGState (context);
flip();
applyTransform (transform);
CGAffineTransform t = state->fontTransform;
t.d = -t.d;
CGContextSetTextMatrix (context, t);
CGGlyph g = (CGGlyph) glyphNumber;
CGContextShowGlyphsAtPoint (context, 0, 0, &g, 1);
CGContextRestoreGState (context);
}
#if JUCE_CLANG
#pragma clang diagnostic pop
#endif
}
else
{
Path p;
Font& f = state->font;
f.getTypeface()->getOutlineForGlyph (glyphNumber, p);
fillPath (p, AffineTransform::scale (f.getHeight() * f.getHorizontalScale(), f.getHeight())
.followedBy (transform));
}
}
bool CoreGraphicsContext::drawTextLayout (const AttributedString& text, const Rectangle<float>& area)
{
CoreTextTypeLayout::drawToCGContext (text, area, context, (float) flipHeight);
return true;
}
CoreGraphicsContext::SavedState::SavedState()
: font (1.0f), fontRef (0), fontTransform (CGAffineTransformIdentity), gradient (0)
{
}
CoreGraphicsContext::SavedState::SavedState (const SavedState& other)
: fillType (other.fillType), font (other.font), fontRef (other.fontRef),
fontTransform (other.fontTransform), gradient (other.gradient)
{
if (gradient != 0)
CGGradientRetain (gradient);
}
CoreGraphicsContext::SavedState::~SavedState()
{
if (gradient != 0)
CGGradientRelease (gradient);
}
void CoreGraphicsContext::SavedState::setFill (const FillType& newFill)
{
fillType = newFill;
if (gradient != 0)
{
CGGradientRelease (gradient);
gradient = 0;
}
}
static CGGradientRef createGradient (const ColourGradient& g, CGColorSpaceRef colourSpace)
{
const int numColours = g.getNumColours();
CGFloat* const data = (CGFloat*) alloca ((size_t) numColours * 5 * sizeof (CGFloat));
CGFloat* const locations = data;
CGFloat* const components = data + numColours;
CGFloat* comps = components;
for (int i = 0; i < numColours; ++i)
{
const Colour colour (g.getColour (i));
*comps++ = (CGFloat) colour.getFloatRed();
*comps++ = (CGFloat) colour.getFloatGreen();
*comps++ = (CGFloat) colour.getFloatBlue();
*comps++ = (CGFloat) colour.getFloatAlpha();
locations[i] = (CGFloat) g.getColourPosition (i);
// There's a bug (?) in the way the CG renderer works where it seems
// to go wrong if you have two colour stops both at position 0..
jassert (i == 0 || locations[i] != 0);
}
return CGGradientCreateWithColorComponents (colourSpace, components, locations, (size_t) numColours);
}
void CoreGraphicsContext::drawGradient()
{
flip();
applyTransform (state->fillType.transform);
CGContextSetAlpha (context, state->fillType.getOpacity());
const ColourGradient& g = *state->fillType.gradient;
CGPoint p1 (convertToCGPoint (g.point1));
CGPoint p2 (convertToCGPoint (g.point2));
state->fillType.transform.transformPoints (p1.x, p1.y, p2.x, p2.y);
if (state->gradient == 0)
state->gradient = createGradient (g, rgbColourSpace);
if (g.isRadial)
CGContextDrawRadialGradient (context, state->gradient, p1, 0, p1, g.point1.getDistanceFrom (g.point2),
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
else
CGContextDrawLinearGradient (context, state->gradient, p1, p2,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
}
void CoreGraphicsContext::createPath (const Path& path) const
{
CGContextBeginPath (context);
for (Path::Iterator i (path); i.next();)
{
switch (i.elementType)
{
case Path::Iterator::startNewSubPath: CGContextMoveToPoint (context, i.x1, i.y1); break;
case Path::Iterator::lineTo: CGContextAddLineToPoint (context, i.x1, i.y1); break;
case Path::Iterator::quadraticTo: CGContextAddQuadCurveToPoint (context, i.x1, i.y1, i.x2, i.y2); break;
case Path::Iterator::cubicTo: CGContextAddCurveToPoint (context, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); break;
case Path::Iterator::closePath: CGContextClosePath (context); break;
default: jassertfalse; break;
}
}
}
void CoreGraphicsContext::createPath (const Path& path, const AffineTransform& transform) const
{
CGContextBeginPath (context);
for (Path::Iterator i (path); i.next();)
{
switch (i.elementType)
{
case Path::Iterator::startNewSubPath:
transform.transformPoint (i.x1, i.y1);
CGContextMoveToPoint (context, i.x1, flipHeight - i.y1);
break;
case Path::Iterator::lineTo:
transform.transformPoint (i.x1, i.y1);
CGContextAddLineToPoint (context, i.x1, flipHeight - i.y1);
break;
case Path::Iterator::quadraticTo:
transform.transformPoints (i.x1, i.y1, i.x2, i.y2);
CGContextAddQuadCurveToPoint (context, i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2);
break;
case Path::Iterator::cubicTo:
transform.transformPoints (i.x1, i.y1, i.x2, i.y2, i.x3, i.y3);
CGContextAddCurveToPoint (context, i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2, i.x3, flipHeight - i.y3);
break;
case Path::Iterator::closePath:
CGContextClosePath (context); break;
default:
jassertfalse;
break;
}
}
}
void CoreGraphicsContext::flip() const
{
CGContextConcatCTM (context, CGAffineTransformMake (1, 0, 0, -1, 0, flipHeight));
}
void CoreGraphicsContext::applyTransform (const AffineTransform& transform) const
{
CGAffineTransform t;
t.a = transform.mat00;
t.b = transform.mat10;
t.c = transform.mat01;
t.d = transform.mat11;
t.tx = transform.mat02;
t.ty = transform.mat12;
CGContextConcatCTM (context, t);
}
//==============================================================================
#if USE_COREGRAPHICS_RENDERING && JUCE_USE_COREIMAGE_LOADER
Image juce_loadWithCoreImage (InputStream& input)
{
MemoryBlock data;
input.readIntoMemoryBlock (data, -1);
#if JUCE_IOS
JUCE_AUTORELEASEPOOL
#endif
{
#if JUCE_IOS
if (UIImage* uiImage = [UIImage imageWithData: [NSData dataWithBytesNoCopy: data.getData()
length: data.getSize()
freeWhenDone: NO]])
{
CGImageRef loadedImage = uiImage.CGImage;
#else
CGDataProviderRef provider = CGDataProviderCreateWithData (0, data.getData(), data.getSize(), 0);
CGImageSourceRef imageSource = CGImageSourceCreateWithDataProvider (provider, 0);
CGDataProviderRelease (provider);
if (imageSource != 0)
{
CGImageRef loadedImage = CGImageSourceCreateImageAtIndex (imageSource, 0, 0);
CFRelease (imageSource);
#endif
if (loadedImage != 0)
{
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo (loadedImage);
const bool hasAlphaChan = (alphaInfo != kCGImageAlphaNone
&& alphaInfo != kCGImageAlphaNoneSkipLast
&& alphaInfo != kCGImageAlphaNoneSkipFirst);
Image image (NativeImageType().create (Image::ARGB, // (CoreImage doesn't work with 24-bit images)
(int) CGImageGetWidth (loadedImage),
(int) CGImageGetHeight (loadedImage),
hasAlphaChan));
CoreGraphicsImage* const cgImage = dynamic_cast<CoreGraphicsImage*> (image.getPixelData());
jassert (cgImage != nullptr); // if USE_COREGRAPHICS_RENDERING is set, the CoreGraphicsImage class should have been used.
CGContextDrawImage (cgImage->context, convertToCGRect (image.getBounds()), loadedImage);
CGContextFlush (cgImage->context);
#if ! JUCE_IOS
CFRelease (loadedImage);
#endif
// Because it's impossible to create a truly 24-bit CG image, this flag allows a user
// to find out whether the file they just loaded the image from had an alpha channel or not.
image.getProperties()->set ("originalImageHadAlpha", hasAlphaChan);
return image;
}
}
}
return Image();
}
#endif
Image juce_createImageFromCIImage (CIImage*, int, int);
Image juce_createImageFromCIImage (CIImage* im, int w, int h)
{
CoreGraphicsImage* cgImage = new CoreGraphicsImage (Image::ARGB, w, h, false);
CIContext* cic = [CIContext contextWithCGContext: cgImage->context options: nil];
[cic drawImage: im inRect: CGRectMake (0, 0, w, h) fromRect: CGRectMake (0, 0, w, h)];
CGContextFlush (cgImage->context);
return Image (cgImage);
}
CGImageRef juce_createCoreGraphicsImage (const Image& juceImage, CGColorSpaceRef colourSpace,
const bool mustOutliveSource)
{
return CoreGraphicsImage::createImage (juceImage, colourSpace, mustOutliveSource);
}
CGContextRef juce_getImageContext (const Image& image)
{
if (CoreGraphicsImage* const cgi = dynamic_cast<CoreGraphicsImage*> (image.getPixelData()))
return cgi->context;
jassertfalse;
return 0;
}
#if JUCE_IOS
Image juce_createImageFromUIImage (UIImage* img)
{
CGImageRef image = [img CGImage];
Image retval (Image::ARGB, (int) CGImageGetWidth (image), (int) CGImageGetHeight (image), true);
CGContextRef ctx = juce_getImageContext (retval);
CGContextDrawImage (ctx, CGRectMake (0.0f, 0.0f, CGImageGetWidth (image), CGImageGetHeight (image)), image);
return retval;
}
#endif
#if JUCE_MAC
NSImage* imageToNSImage (const Image& image, float scaleFactor)
{
JUCE_AUTORELEASEPOOL
{
NSImage* im = [[NSImage alloc] init];
auto requiredSize = NSMakeSize (image.getWidth() / scaleFactor, image.getHeight() / scaleFactor);
[im setSize: requiredSize];
CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
CGImageRef imageRef = juce_createCoreGraphicsImage (image, colourSpace, true);
CGColorSpaceRelease (colourSpace);
NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage: imageRef];
[imageRep setSize: requiredSize];
[im addRepresentation: imageRep];
[imageRep release];
CGImageRelease (imageRef);
return im;
}
}
#endif
}

View File

@ -0,0 +1,69 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
namespace
{
template <class RectType>
Rectangle<int> convertToRectInt (RectType r) noexcept
{
return Rectangle<int> ((int) r.origin.x, (int) r.origin.y, (int) r.size.width, (int) r.size.height);
}
template <class RectType>
Rectangle<float> convertToRectFloat (RectType r) noexcept
{
return Rectangle<float> (r.origin.x, r.origin.y, r.size.width, r.size.height);
}
template <class RectType>
CGRect convertToCGRect (RectType r) noexcept
{
return CGRectMake ((CGFloat) r.getX(), (CGFloat) r.getY(), (CGFloat) r.getWidth(), (CGFloat) r.getHeight());
}
template <typename PointType>
CGPoint convertToCGPoint (PointType p) noexcept
{
return CGPointMake ((CGFloat) p.x, (CGFloat) p.y);
}
}
CGImageRef juce_createCoreGraphicsImage (const Image&, CGColorSpaceRef, bool mustOutliveSource);
CGContextRef juce_getImageContext (const Image&);
#if JUCE_IOS
Image juce_createImageFromUIImage (UIImage*);
#endif
#if JUCE_MAC
NSImage* imageToNSImage (const Image& image, float scaleFactor = 1.0f);
#endif
} // namespace juce

View File

@ -0,0 +1,856 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
static constexpr float referenceFontSize = 1024.0f;
static CTFontRef getCTFontFromTypeface (const Font&);
namespace CoreTextTypeLayout
{
static String findBestAvailableStyle (const Font& font, CGAffineTransform& requiredTransform)
{
auto availableStyles = Font::findAllTypefaceStyles (font.getTypefaceName());
auto style = font.getTypefaceStyle();
if (! availableStyles.contains (style))
{
if (font.isItalic()) // Fake-up an italic font if there isn't a real one.
requiredTransform = CGAffineTransformMake (1.0f, 0, 0.25f, 1.0f, 0, 0);
return availableStyles[0];
}
return style;
}
static float getFontTotalHeight (CTFontRef font)
{
return std::abs ((float) CTFontGetAscent (font))
+ std::abs ((float) CTFontGetDescent (font));
}
static float getHeightToPointsFactor (CTFontRef font)
{
return referenceFontSize / getFontTotalHeight (font);
}
static CTFontRef getFontWithPointSize (CTFontRef font, float size)
{
auto newFont = CTFontCreateCopyWithAttributes (font, size, nullptr, nullptr);
CFRelease (font);
return newFont;
}
static CTFontRef createCTFont (const Font& font, const float fontSizePoints, CGAffineTransform& transformRequired)
{
auto cfFontFamily = FontStyleHelpers::getConcreteFamilyName (font).toCFString();
auto cfFontStyle = findBestAvailableStyle (font, transformRequired).toCFString();
CFStringRef keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute };
CFTypeRef values[] = { cfFontFamily, cfFontStyle };
auto fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys,
(const void**) &values,
numElementsInArray (keys),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFRelease (cfFontStyle);
auto ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes);
CFRelease (fontDescAttributes);
auto ctFontRef = CTFontCreateWithFontDescriptor (ctFontDescRef, fontSizePoints, nullptr);
CFRelease (ctFontDescRef);
CFRelease (cfFontFamily);
return ctFontRef;
}
//==============================================================================
struct Advances
{
Advances (CTRunRef run, CFIndex numGlyphs) : advances (CTRunGetAdvancesPtr (run))
{
if (advances == nullptr)
{
local.malloc (numGlyphs);
CTRunGetAdvances (run, CFRangeMake (0, 0), local);
advances = local;
}
}
const CGSize* advances;
HeapBlock<CGSize> local;
};
struct Glyphs
{
Glyphs (CTRunRef run, size_t numGlyphs) : glyphs (CTRunGetGlyphsPtr (run))
{
if (glyphs == nullptr)
{
local.malloc (numGlyphs);
CTRunGetGlyphs (run, CFRangeMake (0, 0), local);
glyphs = local;
}
}
const CGGlyph* glyphs;
HeapBlock<CGGlyph> local;
};
struct Positions
{
Positions (CTRunRef run, size_t numGlyphs) : points (CTRunGetPositionsPtr (run))
{
if (points == nullptr)
{
local.malloc (numGlyphs);
CTRunGetPositions (run, CFRangeMake (0, 0), local);
points = local;
}
}
const CGPoint* points;
HeapBlock<CGPoint> local;
};
struct LineInfo
{
LineInfo (CTFrameRef frame, CTLineRef line, CFIndex lineIndex)
{
CTFrameGetLineOrigins (frame, CFRangeMake (lineIndex, 1), &origin);
CTLineGetTypographicBounds (line, &ascent, &descent, &leading);
}
CGPoint origin;
CGFloat ascent, descent, leading;
};
static CTFontRef getOrCreateFont (const Font& f)
{
if (auto ctf = getCTFontFromTypeface (f))
{
CFRetain (ctf);
return ctf;
}
CGAffineTransform transform;
return createCTFont (f, referenceFontSize, transform);
}
//==============================================================================
static CTTextAlignment getTextAlignment (const AttributedString& text)
{
switch (text.getJustification().getOnlyHorizontalFlags())
{
#if defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
case Justification::right: return kCTTextAlignmentRight;
case Justification::horizontallyCentred: return kCTTextAlignmentCenter;
case Justification::horizontallyJustified: return kCTTextAlignmentJustified;
default: return kCTTextAlignmentLeft;
#else
case Justification::right: return kCTRightTextAlignment;
case Justification::horizontallyCentred: return kCTCenterTextAlignment;
case Justification::horizontallyJustified: return kCTJustifiedTextAlignment;
default: return kCTLeftTextAlignment;
#endif
}
}
static CTLineBreakMode getLineBreakMode (const AttributedString& text)
{
switch (text.getWordWrap())
{
case AttributedString::none: return kCTLineBreakByClipping;
case AttributedString::byChar: return kCTLineBreakByCharWrapping;
default: return kCTLineBreakByWordWrapping;
}
}
static CTWritingDirection getWritingDirection (const AttributedString& text)
{
switch (text.getReadingDirection())
{
case AttributedString::rightToLeft: return kCTWritingDirectionRightToLeft;
case AttributedString::leftToRight: return kCTWritingDirectionLeftToRight;
default: return kCTWritingDirectionNatural;
}
}
//==============================================================================
static CFAttributedStringRef createCFAttributedString (const AttributedString& text)
{
#if JUCE_IOS
auto rgbColourSpace = CGColorSpaceCreateDeviceRGB();
#endif
auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0);
auto cfText = text.getText().toCFString();
CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText);
CFRelease (cfText);
auto numCharacterAttributes = text.getNumAttributes();
auto attribStringLen = CFAttributedStringGetLength (attribString);
for (int i = 0; i < numCharacterAttributes; ++i)
{
auto& attr = text.getAttribute (i);
auto rangeStart = attr.range.getStart();
if (rangeStart >= attribStringLen)
continue;
auto range = CFRangeMake (rangeStart, jmin (attr.range.getEnd(), (int) attribStringLen) - rangeStart);
if (auto ctFontRef = getOrCreateFont (attr.font))
{
ctFontRef = getFontWithPointSize (ctFontRef, attr.font.getHeight() * getHeightToPointsFactor (ctFontRef));
CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef);
auto extraKerning = attr.font.getExtraKerningFactor();
if (extraKerning != 0)
{
extraKerning *= attr.font.getHeight();
auto numberRef = CFNumberCreate (0, kCFNumberFloatType, &extraKerning);
CFAttributedStringSetAttribute (attribString, range, kCTKernAttributeName, numberRef);
CFRelease (numberRef);
}
CFRelease (ctFontRef);
}
{
auto col = attr.colour;
#if JUCE_IOS
const CGFloat components[] = { col.getFloatRed(),
col.getFloatGreen(),
col.getFloatBlue(),
col.getFloatAlpha() };
auto colour = CGColorCreate (rgbColourSpace, components);
#else
auto colour = CGColorCreateGenericRGB (col.getFloatRed(),
col.getFloatGreen(),
col.getFloatBlue(),
col.getFloatAlpha());
#endif
CFAttributedStringSetAttribute (attribString, range, kCTForegroundColorAttributeName, colour);
CGColorRelease (colour);
}
}
// Paragraph Attributes
auto ctTextAlignment = getTextAlignment (text);
auto ctLineBreakMode = getLineBreakMode (text);
auto ctWritingDirection = getWritingDirection (text);
CGFloat ctLineSpacing = text.getLineSpacing();
CTParagraphStyleSetting settings[] =
{
{ kCTParagraphStyleSpecifierAlignment, sizeof (CTTextAlignment), &ctTextAlignment },
{ kCTParagraphStyleSpecifierLineBreakMode, sizeof (CTLineBreakMode), &ctLineBreakMode },
{ kCTParagraphStyleSpecifierBaseWritingDirection, sizeof (CTWritingDirection), &ctWritingDirection},
{ kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &ctLineSpacing }
};
auto ctParagraphStyleRef = CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings));
CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)),
kCTParagraphStyleAttributeName, ctParagraphStyleRef);
CFRelease (ctParagraphStyleRef);
#if JUCE_IOS
CGColorSpaceRelease (rgbColourSpace);
#endif
return attribString;
}
static CTFrameRef createCTFrame (const AttributedString& text, CGRect bounds)
{
auto attribString = createCFAttributedString (text);
auto framesetter = CTFramesetterCreateWithAttributedString (attribString);
CFRelease (attribString);
auto path = CGPathCreateMutable();
CGPathAddRect (path, nullptr, bounds);
auto frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr);
CFRelease (framesetter);
CGPathRelease (path);
return frame;
}
static Range<float> getLineVerticalRange (CTFrameRef frame, CFArrayRef lines, int lineIndex)
{
LineInfo info (frame, (CTLineRef) CFArrayGetValueAtIndex (lines, lineIndex), lineIndex);
return { (float) (info.origin.y - info.descent),
(float) (info.origin.y + info.ascent) };
}
static float findCTFrameHeight (CTFrameRef frame)
{
auto lines = CTFrameGetLines (frame);
auto numLines = CFArrayGetCount (lines);
if (numLines == 0)
return 0;
auto range = getLineVerticalRange (frame, lines, 0);
if (numLines > 1)
range = range.getUnionWith (getLineVerticalRange (frame, lines, (int) numLines - 1));
return range.getLength();
}
static void drawToCGContext (const AttributedString& text, const Rectangle<float>& area,
const CGContextRef& context, float flipHeight)
{
Rectangle<float> ctFrameArea;
auto verticalJustification = text.getJustification().getOnlyVerticalFlags();
// Ugly hack to fix a bug in OS X Sierra where the CTFrame needs to be slightly
// larger than the font height - otherwise the CTFrame will be invalid
if (verticalJustification == Justification::verticallyCentred)
ctFrameArea = area.withSizeKeepingCentre (area.getWidth(), area.getHeight() * 1.1f);
else if (verticalJustification == Justification::bottom)
ctFrameArea = area.withTop (area.getY() - (area.getHeight() * 0.1f));
else
ctFrameArea = area.withHeight (area.getHeight() * 1.1f);
auto frame = createCTFrame (text, CGRectMake ((CGFloat) ctFrameArea.getX(), flipHeight - (CGFloat) ctFrameArea.getBottom(),
(CGFloat) ctFrameArea.getWidth(), (CGFloat) ctFrameArea.getHeight()));
if (verticalJustification == Justification::verticallyCentred
|| verticalJustification == Justification::bottom)
{
auto adjust = ctFrameArea.getHeight() - findCTFrameHeight (frame);
if (verticalJustification == Justification::verticallyCentred)
adjust *= 0.5f;
CGContextSaveGState (context);
CGContextTranslateCTM (context, 0, -adjust);
CTFrameDraw (frame, context);
CGContextRestoreGState (context);
}
else
{
CTFrameDraw (frame, context);
}
CFRelease (frame);
}
static void createLayout (TextLayout& glyphLayout, const AttributedString& text)
{
auto boundsHeight = glyphLayout.getHeight();
auto frame = createCTFrame (text, CGRectMake (0, 0, glyphLayout.getWidth(), boundsHeight));
auto lines = CTFrameGetLines (frame);
auto numLines = CFArrayGetCount (lines);
glyphLayout.ensureStorageAllocated ((int) numLines);
for (CFIndex i = 0; i < numLines; ++i)
{
auto line = (CTLineRef) CFArrayGetValueAtIndex (lines, i);
auto runs = CTLineGetGlyphRuns (line);
auto numRuns = CFArrayGetCount (runs);
auto cfrlineStringRange = CTLineGetStringRange (line);
auto lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length - 1;
Range<int> lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd);
LineInfo lineInfo (frame, line, i);
auto glyphLine = new TextLayout::Line (lineStringRange,
Point<float> ((float) lineInfo.origin.x,
(float) (boundsHeight - lineInfo.origin.y)),
(float) lineInfo.ascent,
(float) lineInfo.descent,
(float) lineInfo.leading,
(int) numRuns);
glyphLayout.addLine (glyphLine);
for (CFIndex j = 0; j < numRuns; ++j)
{
auto run = (CTRunRef) CFArrayGetValueAtIndex (runs, j);
auto numGlyphs = CTRunGetGlyphCount (run);
auto runStringRange = CTRunGetStringRange (run);
auto glyphRun = new TextLayout::Run (Range<int> ((int) runStringRange.location,
(int) (runStringRange.location + runStringRange.length - 1)),
(int) numGlyphs);
glyphLine->runs.add (glyphRun);
CFDictionaryRef runAttributes = CTRunGetAttributes (run);
CTFontRef ctRunFont;
if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void**) &ctRunFont))
{
auto cfsFontName = CTFontCopyPostScriptName (ctRunFont);
auto ctFontRef = CTFontCreateWithName (cfsFontName, referenceFontSize, nullptr);
CFRelease (cfsFontName);
auto fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef);
CFRelease (ctFontRef);
auto cfsFontFamily = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute);
auto cfsFontStyle = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontStyleNameAttribute);
glyphRun->font = Font (String::fromCFString (cfsFontFamily),
String::fromCFString (cfsFontStyle),
(float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor));
CFRelease (cfsFontStyle);
CFRelease (cfsFontFamily);
}
CGColorRef cgRunColor;
if (CFDictionaryGetValueIfPresent (runAttributes, kCTForegroundColorAttributeName, (const void**) &cgRunColor)
&& CGColorGetNumberOfComponents (cgRunColor) == 4)
{
auto* components = CGColorGetComponents (cgRunColor);
glyphRun->colour = Colour::fromFloatRGBA ((float) components[0],
(float) components[1],
(float) components[2],
(float) components[3]);
}
const Glyphs glyphs (run, (size_t) numGlyphs);
const Advances advances (run, numGlyphs);
const Positions positions (run, (size_t) numGlyphs);
for (CFIndex k = 0; k < numGlyphs; ++k)
glyphRun->glyphs.add (TextLayout::Glyph (glyphs.glyphs[k], Point<float> ((float) positions.points[k].x,
(float) positions.points[k].y),
(float) advances.advances[k].width));
}
}
CFRelease (frame);
}
}
//==============================================================================
class OSXTypeface : public Typeface
{
public:
OSXTypeface (const Font& font)
: Typeface (font.getTypefaceName(), font.getTypefaceStyle()), canBeUsedForLayout (true)
{
ctFontRef = CoreTextTypeLayout::createCTFont (font, referenceFontSize, renderingTransform);
if (ctFontRef != nullptr)
{
fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr);
initialiseMetrics();
}
}
OSXTypeface (const void* data, size_t dataSize)
: Typeface ({}, {}), canBeUsedForLayout (false), dataCopy (data, dataSize)
{
// We can't use CFDataCreate here as this triggers a false positive in ASAN
// so copy the data manually and use CFDataCreateWithBytesNoCopy
auto cfData = CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) dataCopy.getData(),
(CFIndex) dataCopy.getSize(), kCFAllocatorNull);
auto provider = CGDataProviderCreateWithCFData (cfData);
CFRelease (cfData);
#if JUCE_IOS
// Workaround for a an obscure iOS bug which can cause the app to dead-lock
// when loading custom type faces. See: http://www.openradar.me/18778790 and
// http://stackoverflow.com/questions/40242370/app-hangs-in-simulator
[UIFont systemFontOfSize: 12];
#endif
fontRef = CGFontCreateWithDataProvider (provider);
CGDataProviderRelease (provider);
if (fontRef != nullptr)
{
#if JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8
canBeUsedForLayout = CTFontManagerRegisterGraphicsFont (fontRef, nullptr);
#endif
ctFontRef = CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr);
if (ctFontRef != nullptr)
{
if (auto fontName = CTFontCopyName (ctFontRef, kCTFontFamilyNameKey))
{
name = String::fromCFString (fontName);
CFRelease (fontName);
}
if (auto fontStyle = CTFontCopyName (ctFontRef, kCTFontStyleNameKey))
{
style = String::fromCFString (fontStyle);
CFRelease (fontStyle);
}
initialiseMetrics();
}
}
}
void initialiseMetrics()
{
auto ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef));
auto ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef));
auto ctTotalHeight = ctAscent + ctDescent;
ascent = ctAscent / ctTotalHeight;
unitsToHeightScaleFactor = 1.0f / ctTotalHeight;
pathTransform = AffineTransform::scale (unitsToHeightScaleFactor);
fontHeightToPointsFactor = referenceFontSize / ctTotalHeight;
const short zero = 0;
auto numberRef = CFNumberCreate (0, kCFNumberShortType, &zero);
CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName };
CFTypeRef values[] = { ctFontRef, numberRef };
attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys,
(const void**) &values, numElementsInArray (keys),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFRelease (numberRef);
}
~OSXTypeface()
{
if (attributedStringAtts != nullptr)
CFRelease (attributedStringAtts);
if (fontRef != nullptr)
{
#if JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8
CTFontManagerUnregisterGraphicsFont (fontRef, nullptr);
#endif
CGFontRelease (fontRef);
}
if (ctFontRef != nullptr)
CFRelease (ctFontRef);
}
float getAscent() const override { return ascent; }
float getDescent() const override { return 1.0f - ascent; }
float getHeightToPointsFactor() const override { return fontHeightToPointsFactor; }
float getStringWidth (const String& text) override
{
float x = 0;
if (ctFontRef != nullptr && text.isNotEmpty())
{
auto cfText = text.toCFString();
auto attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts);
CFRelease (cfText);
auto line = CTLineCreateWithAttributedString (attribString);
auto runArray = CTLineGetGlyphRuns (line);
for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
{
auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
auto length = CTRunGetGlyphCount (run);
const CoreTextTypeLayout::Advances advances (run, length);
for (int j = 0; j < length; ++j)
x += (float) advances.advances[j].width;
}
CFRelease (line);
CFRelease (attribString);
x *= unitsToHeightScaleFactor;
}
return x;
}
void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets) override
{
xOffsets.add (0);
if (ctFontRef != nullptr && text.isNotEmpty())
{
float x = 0;
auto cfText = text.toCFString();
auto attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts);
CFRelease (cfText);
auto line = CTLineCreateWithAttributedString (attribString);
auto runArray = CTLineGetGlyphRuns (line);
for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
{
auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
auto length = CTRunGetGlyphCount (run);
const CoreTextTypeLayout::Advances advances (run, length);
const CoreTextTypeLayout::Glyphs glyphs (run, (size_t) length);
for (int j = 0; j < length; ++j)
{
x += (float) advances.advances[j].width;
xOffsets.add (x * unitsToHeightScaleFactor);
resultGlyphs.add (glyphs.glyphs[j]);
}
}
CFRelease (line);
CFRelease (attribString);
}
}
bool getOutlineForGlyph (int glyphNumber, Path& path) override
{
jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty
if (auto pathRef = CTFontCreatePathForGlyph (ctFontRef, (CGGlyph) glyphNumber, &renderingTransform))
{
CGPathApply (pathRef, &path, pathApplier);
CFRelease (pathRef);
if (! pathTransform.isIdentity())
path.applyTransform (pathTransform);
return true;
}
return false;
}
//==============================================================================
CGFontRef fontRef = {};
CTFontRef ctFontRef = {};
float fontHeightToPointsFactor = 1.0f;
CGAffineTransform renderingTransform = CGAffineTransformIdentity;
bool canBeUsedForLayout;
private:
MemoryBlock dataCopy;
CFDictionaryRef attributedStringAtts = {};
float ascent = 0, unitsToHeightScaleFactor = 0;
AffineTransform pathTransform;
static void pathApplier (void* info, const CGPathElement* element)
{
auto& path = *static_cast<Path*> (info);
auto* p = element->points;
switch (element->type)
{
case kCGPathElementMoveToPoint: path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break;
case kCGPathElementAddLineToPoint: path.lineTo ((float) p[0].x, (float) -p[0].y); break;
case kCGPathElementAddQuadCurveToPoint: path.quadraticTo ((float) p[0].x, (float) -p[0].y,
(float) p[1].x, (float) -p[1].y); break;
case kCGPathElementAddCurveToPoint: path.cubicTo ((float) p[0].x, (float) -p[0].y,
(float) p[1].x, (float) -p[1].y,
(float) p[2].x, (float) -p[2].y); break;
case kCGPathElementCloseSubpath: path.closeSubPath(); break;
default: jassertfalse; break;
}
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXTypeface)
};
CTFontRef getCTFontFromTypeface (const Font& f)
{
if (auto* tf = dynamic_cast<OSXTypeface*> (f.getTypeface()))
return tf->ctFontRef;
return {};
}
StringArray Font::findAllTypefaceNames()
{
StringArray names;
#if JUCE_MAC
// CTFontManager only exists on OS X 10.6 and later, it does not exist on iOS
auto fontFamilyArray = CTFontManagerCopyAvailableFontFamilyNames();
for (CFIndex i = 0; i < CFArrayGetCount (fontFamilyArray); ++i)
{
auto family = String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (fontFamilyArray, i));
if (! family.startsWithChar ('.')) // ignore fonts that start with a '.'
names.addIfNotAlreadyThere (family);
}
CFRelease (fontFamilyArray);
#else
auto fontCollectionRef = CTFontCollectionCreateFromAvailableFonts (nullptr);
auto fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef);
CFRelease (fontCollectionRef);
for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i)
{
auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i);
auto cfsFontFamily = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontFamilyNameAttribute);
names.addIfNotAlreadyThere (String::fromCFString (cfsFontFamily));
CFRelease (cfsFontFamily);
}
CFRelease (fontDescriptorArray);
#endif
names.sort (true);
return names;
}
StringArray Font::findAllTypefaceStyles (const String& family)
{
if (FontStyleHelpers::isPlaceholderFamilyName (family))
return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));
StringArray results;
auto cfsFontFamily = family.toCFString();
CFStringRef keys[] = { kCTFontFamilyNameAttribute };
CFTypeRef values[] = { cfsFontFamily };
auto fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFRelease (cfsFontFamily);
auto ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes);
CFRelease (fontDescAttributes);
auto fontFamilyArray = CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks);
CFRelease (ctFontDescRef);
auto fontCollectionRef = CTFontCollectionCreateWithFontDescriptors (fontFamilyArray, nullptr);
CFRelease (fontFamilyArray);
auto fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef);
CFRelease (fontCollectionRef);
if (fontDescriptorArray != nullptr)
{
for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i)
{
auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i);
auto cfsFontStyle = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontStyleNameAttribute);
results.add (String::fromCFString (cfsFontStyle));
CFRelease (cfsFontStyle);
}
CFRelease (fontDescriptorArray);
}
return results;
}
//==============================================================================
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { return new OSXTypeface (font); }
Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size) { return new OSXTypeface (data, size); }
void Typeface::scanFolderForFonts (const File&)
{
jassertfalse; // not implemented on this platform
}
struct DefaultFontNames
{
#if JUCE_IOS
String defaultSans { "Helvetica" },
defaultSerif { "Times New Roman" },
defaultFixed { "Courier New" };
#else
String defaultSans { "Lucida Grande" },
defaultSerif { "Times New Roman" },
defaultFixed { "Menlo" };
#endif
};
Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
{
static DefaultFontNames defaultNames;
auto newFont = font;
auto& faceName = font.getTypefaceName();
if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans);
else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif);
else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed);
if (font.getTypefaceStyle() == getDefaultStyle())
newFont.setTypefaceStyle ("Regular");
return Typeface::createSystemTypefaceFor (newFont);
}
static bool canAllTypefacesBeUsedInLayout (const AttributedString& text)
{
auto numCharacterAttributes = text.getNumAttributes();
for (int i = 0; i < numCharacterAttributes; ++i)
{
if (auto tf = dynamic_cast<OSXTypeface*> (text.getAttribute(i).font.getTypeface()))
if (tf->canBeUsedForLayout)
continue;
return false;
}
return true;
}
bool TextLayout::createNativeLayout (const AttributedString& text)
{
if (canAllTypefacesBeUsedInLayout (text))
{
CoreTextTypeLayout::createLayout (*this, text);
return true;
}
return false;
}
} // namespace juce

View File

@ -0,0 +1,152 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
extern Image JUCE_API getIconFromApplication (const String&, int);
static Image getIconFromIcnsFile (const File& icnsFile, const int size)
{
FileInputStream stream (icnsFile);
if (! stream.openedOk())
return {};
const int numHeaderSectionBytes = 4;
char headerSection [numHeaderSectionBytes];
if (stream.read (headerSection, numHeaderSectionBytes) != numHeaderSectionBytes
|| headerSection[0] != 'i'
|| headerSection[1] != 'c'
|| headerSection[2] != 'n'
|| headerSection[3] != 's')
return {};
if (stream.read (headerSection, numHeaderSectionBytes) != numHeaderSectionBytes)
return {};
const auto dataSize = juce::ByteOrder::bigEndianInt (headerSection);
if (dataSize <= 0)
return {};
OwnedArray<juce::ImageFileFormat> internalFormats;
internalFormats.add (new PNGImageFormat());
internalFormats.add (new JPEGImageFormat());
Array<Image> images;
auto maxWidth = 0;
auto maxWidthIndex = -1;
while (stream.getPosition() < dataSize)
{
const auto sectionStart = stream.getPosition();
if (! stream.setPosition (sectionStart + 4))
break;
if (stream.read (headerSection, numHeaderSectionBytes) != numHeaderSectionBytes)
break;
const auto sectionSize = ByteOrder::bigEndianInt (headerSection);
if (sectionSize <= 0)
break;
const auto sectionDataStart = stream.getPosition();
for (auto* fmt : internalFormats)
{
if (fmt->canUnderstand (stream))
{
stream.setPosition (sectionDataStart);
images.add (fmt->decodeImage (stream));
const auto lastImageIndex = images.size() - 1;
const auto lastWidth = images.getReference (lastImageIndex).getWidth();
if (lastWidth > maxWidth)
{
maxWidthIndex = lastImageIndex;
maxWidth = lastWidth;
}
}
stream.setPosition (sectionDataStart);
}
stream.setPosition (sectionStart + sectionSize);
}
return maxWidthIndex == -1 ? juce::Image()
: images.getReference (maxWidthIndex).rescaled (size, size, Graphics::ResamplingQuality::highResamplingQuality);
}
Image JUCE_API getIconFromApplication (const String& applicationPath, const int size)
{
Image hostIcon;
if (CFStringRef pathCFString = CFStringCreateWithCString (kCFAllocatorDefault, applicationPath.toRawUTF8(), kCFStringEncodingUTF8))
{
if (CFURLRef url = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, pathCFString, kCFURLPOSIXPathStyle, 1))
{
if (CFBundleRef appBundle = CFBundleCreate (kCFAllocatorDefault, url))
{
if (CFTypeRef infoValue = CFBundleGetValueForInfoDictionaryKey (appBundle, CFSTR("CFBundleIconFile")))
{
if (CFGetTypeID (infoValue) == CFStringGetTypeID())
{
CFStringRef iconFilename = reinterpret_cast<CFStringRef> (infoValue);
CFStringRef resourceURLSuffix = CFStringHasSuffix (iconFilename, CFSTR(".icns")) ? nullptr : CFSTR("icns");
if (CFURLRef iconURL = CFBundleCopyResourceURL (appBundle, iconFilename, resourceURLSuffix, nullptr))
{
if (CFStringRef iconPath = CFURLCopyFileSystemPath (iconURL, kCFURLPOSIXPathStyle))
{
File icnsFile (CFStringGetCStringPtr (iconPath, CFStringGetSystemEncoding()));
hostIcon = getIconFromIcnsFile (icnsFile, size);
CFRelease (iconPath);
}
CFRelease (iconURL);
}
}
}
CFRelease (appBundle);
}
CFRelease (url);
}
CFRelease (pathCFString);
}
return hostIcon;
}
} // namespace juce

View File

@ -0,0 +1,830 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
template <typename Type>
D2D1_RECT_F rectangleToRectF (const Rectangle<Type>& r)
{
return D2D1::RectF ((float) r.getX(), (float) r.getY(), (float) r.getRight(), (float) r.getBottom());
}
static D2D1_COLOR_F colourToD2D (Colour c)
{
return D2D1::ColorF::ColorF (c.getFloatRed(), c.getFloatGreen(), c.getFloatBlue(), c.getFloatAlpha());
}
static void pathToGeometrySink (const Path& path, ID2D1GeometrySink* sink, const AffineTransform& transform)
{
Path::Iterator it (path);
while (it.next())
{
switch (it.elementType)
{
case Path::Iterator::cubicTo:
{
D2D1_BEZIER_SEGMENT seg;
transform.transformPoint (it.x1, it.y1);
seg.point1 = D2D1::Point2F (it.x1, it.y1);
transform.transformPoint (it.x2, it.y2);
seg.point2 = D2D1::Point2F (it.x2, it.y2);
transform.transformPoint (it.x3, it.y3);
seg.point3 = D2D1::Point2F (it.x3, it.y3);
sink->AddBezier (seg);
break;
}
case Path::Iterator::lineTo:
{
transform.transformPoint (it.x1, it.y1);
sink->AddLine (D2D1::Point2F (it.x1, it.y1));
break;
}
case Path::Iterator::quadraticTo:
{
D2D1_QUADRATIC_BEZIER_SEGMENT seg;
transform.transformPoint (it.x1, it.y1);
seg.point1 = D2D1::Point2F (it.x1, it.y1);
transform.transformPoint (it.x2, it.y2);
seg.point2 = D2D1::Point2F (it.x2, it.y2);
sink->AddQuadraticBezier (seg);
break;
}
case Path::Iterator::closePath:
{
sink->EndFigure (D2D1_FIGURE_END_CLOSED);
break;
}
case Path::Iterator::startNewSubPath:
{
transform.transformPoint (it.x1, it.y1);
sink->BeginFigure (D2D1::Point2F (it.x1, it.y1), D2D1_FIGURE_BEGIN_FILLED);
break;
}
}
}
}
static D2D1::Matrix3x2F transformToMatrix (const AffineTransform& transform)
{
D2D1::Matrix3x2F matrix;
matrix._11 = transform.mat00;
matrix._12 = transform.mat10;
matrix._21 = transform.mat01;
matrix._22 = transform.mat11;
matrix._31 = transform.mat02;
matrix._32 = transform.mat12;
return matrix;
}
static D2D1_POINT_2F pointTransformed (int x, int y, const AffineTransform& transform)
{
transform.transformPoint (x, y);
return D2D1::Point2F ((FLOAT) x, (FLOAT) y);
}
static void rectToGeometrySink (const Rectangle<int>& rect, ID2D1GeometrySink* sink, const AffineTransform& transform)
{
sink->BeginFigure (pointTransformed (rect.getX(), rect.getY(), transform), D2D1_FIGURE_BEGIN_FILLED);
sink->AddLine (pointTransformed (rect.getRight(), rect.getY(), transform));
sink->AddLine (pointTransformed (rect.getRight(), rect.getBottom(), transform));
sink->AddLine (pointTransformed (rect.getX(), rect.getBottom(), transform));
sink->EndFigure (D2D1_FIGURE_END_CLOSED);
}
//==============================================================================
struct Direct2DLowLevelGraphicsContext::Pimpl
{
ID2D1PathGeometry* rectListToPathGeometry (const RectangleList<int>& clipRegion)
{
ID2D1PathGeometry* p = nullptr;
factories->d2dFactory->CreatePathGeometry (&p);
ComSmartPtr<ID2D1GeometrySink> sink;
HRESULT hr = p->Open (sink.resetAndGetPointerAddress()); // xxx handle error
sink->SetFillMode (D2D1_FILL_MODE_WINDING);
for (int i = clipRegion.getNumRectangles(); --i >= 0;)
rectToGeometrySink (clipRegion.getRectangle(i), sink, AffineTransform());
hr = sink->Close();
return p;
}
ID2D1PathGeometry* pathToPathGeometry (const Path& path, const AffineTransform& transform)
{
ID2D1PathGeometry* p = nullptr;
factories->d2dFactory->CreatePathGeometry (&p);
ComSmartPtr<ID2D1GeometrySink> sink;
HRESULT hr = p->Open (sink.resetAndGetPointerAddress());
sink->SetFillMode (D2D1_FILL_MODE_WINDING); // xxx need to check Path::isUsingNonZeroWinding()
pathToGeometrySink (path, sink, transform);
hr = sink->Close();
return p;
}
SharedResourcePointer<Direct2DFactories> factories;
ComSmartPtr<ID2D1HwndRenderTarget> renderingTarget;
ComSmartPtr<ID2D1SolidColorBrush> colourBrush;
};
//==============================================================================
struct Direct2DLowLevelGraphicsContext::SavedState
{
public:
SavedState (Direct2DLowLevelGraphicsContext& owner_)
: owner (owner_)
{
if (owner.currentState != nullptr)
{
// xxx seems like a very slow way to create one of these, and this is a performance
// bottleneck.. Can the same internal objects be shared by multiple state objects, maybe using copy-on-write?
setFill (owner.currentState->fillType);
currentBrush = owner.currentState->currentBrush;
clipRect = owner.currentState->clipRect;
transform = owner.currentState->transform;
font = owner.currentState->font;
currentFontFace = owner.currentState->currentFontFace;
}
else
{
const D2D1_SIZE_U size (owner.pimpl->renderingTarget->GetPixelSize());
clipRect.setSize (size.width, size.height);
setFill (FillType (Colours::black));
}
}
~SavedState()
{
clearClip();
clearFont();
clearFill();
clearPathClip();
clearImageClip();
complexClipLayer = nullptr;
bitmapMaskLayer = nullptr;
}
void clearClip()
{
popClips();
shouldClipRect = false;
}
void clipToRectangle (const Rectangle<int>& r)
{
clearClip();
clipRect = r.toFloat().transformed (transform).getSmallestIntegerContainer();
shouldClipRect = true;
pushClips();
}
void clearPathClip()
{
popClips();
if (shouldClipComplex)
{
complexClipGeometry = nullptr;
shouldClipComplex = false;
}
}
void Direct2DLowLevelGraphicsContext::SavedState::clipToPath (ID2D1Geometry* geometry)
{
clearPathClip();
if (complexClipLayer == nullptr)
owner.pimpl->renderingTarget->CreateLayer (complexClipLayer.resetAndGetPointerAddress());
complexClipGeometry = geometry;
shouldClipComplex = true;
pushClips();
}
void clearRectListClip()
{
popClips();
if (shouldClipRectList)
{
rectListGeometry = nullptr;
shouldClipRectList = false;
}
}
void clipToRectList (ID2D1Geometry* geometry)
{
clearRectListClip();
if (rectListLayer == nullptr)
owner.pimpl->renderingTarget->CreateLayer (rectListLayer.resetAndGetPointerAddress());
rectListGeometry = geometry;
shouldClipRectList = true;
pushClips();
}
void clearImageClip()
{
popClips();
if (shouldClipBitmap)
{
maskBitmap = nullptr;
bitmapMaskBrush = nullptr;
shouldClipBitmap = false;
}
}
void clipToImage (const Image& clipImage, const AffineTransform& clipTransform)
{
clearImageClip();
if (bitmapMaskLayer == nullptr)
owner.pimpl->renderingTarget->CreateLayer (bitmapMaskLayer.resetAndGetPointerAddress());
D2D1_BRUSH_PROPERTIES brushProps;
brushProps.opacity = 1;
brushProps.transform = transformToMatrix (clipTransform);
D2D1_BITMAP_BRUSH_PROPERTIES bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP);
D2D1_SIZE_U size;
size.width = clipImage.getWidth();
size.height = clipImage.getHeight();
D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties();
maskImage = clipImage.convertedToFormat (Image::ARGB);
Image::BitmapData bd (maskImage, Image::BitmapData::readOnly); // xxx should be maskImage?
bp.pixelFormat = owner.pimpl->renderingTarget->GetPixelFormat();
bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
HRESULT hr = owner.pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, maskBitmap.resetAndGetPointerAddress());
hr = owner.pimpl->renderingTarget->CreateBitmapBrush (maskBitmap, bmProps, brushProps, bitmapMaskBrush.resetAndGetPointerAddress());
imageMaskLayerParams = D2D1::LayerParameters();
imageMaskLayerParams.opacityBrush = bitmapMaskBrush;
shouldClipBitmap = true;
pushClips();
}
void popClips()
{
if (clipsBitmap)
{
owner.pimpl->renderingTarget->PopLayer();
clipsBitmap = false;
}
if (clipsComplex)
{
owner.pimpl->renderingTarget->PopLayer();
clipsComplex = false;
}
if (clipsRectList)
{
owner.pimpl->renderingTarget->PopLayer();
clipsRectList = false;
}
if (clipsRect)
{
owner.pimpl->renderingTarget->PopAxisAlignedClip();
clipsRect = false;
}
}
void pushClips()
{
if (shouldClipRect && !clipsRect)
{
owner.pimpl->renderingTarget->PushAxisAlignedClip (rectangleToRectF (clipRect), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
clipsRect = true;
}
if (shouldClipRectList && !clipsRectList)
{
D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters();
rectListGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds);
layerParams.geometricMask = rectListGeometry;
owner.pimpl->renderingTarget->PushLayer (layerParams, rectListLayer);
clipsRectList = true;
}
if (shouldClipComplex && !clipsComplex)
{
D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters();
complexClipGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds);
layerParams.geometricMask = complexClipGeometry;
owner.pimpl->renderingTarget->PushLayer (layerParams, complexClipLayer);
clipsComplex = true;
}
if (shouldClipBitmap && !clipsBitmap)
{
owner.pimpl->renderingTarget->PushLayer (imageMaskLayerParams, bitmapMaskLayer);
clipsBitmap = true;
}
}
void setFill (const FillType& newFillType)
{
if (fillType != newFillType)
{
fillType = newFillType;
clearFill();
}
}
void clearFont()
{
currentFontFace = localFontFace = nullptr;
}
void setFont (const Font& newFont)
{
if (font != newFont)
{
font = newFont;
clearFont();
}
}
void createFont()
{
if (currentFontFace == nullptr)
{
WindowsDirectWriteTypeface* typeface = dynamic_cast<WindowsDirectWriteTypeface*> (font.getTypeface());
currentFontFace = typeface->getIDWriteFontFace();
fontHeightToEmSizeFactor = typeface->getUnitsToHeightScaleFactor();
}
}
void setOpacity (float newOpacity)
{
fillType.setOpacity (newOpacity);
if (currentBrush != nullptr)
currentBrush->SetOpacity (newOpacity);
}
void clearFill()
{
gradientStops = nullptr;
linearGradient = nullptr;
radialGradient = nullptr;
bitmap = nullptr;
bitmapBrush = nullptr;
currentBrush = nullptr;
}
void createBrush()
{
if (currentBrush == nullptr)
{
if (fillType.isColour())
{
D2D1_COLOR_F colour = colourToD2D (fillType.colour);
owner.pimpl->colourBrush->SetColor (colour);
currentBrush = owner.pimpl->colourBrush;
}
else if (fillType.isTiledImage())
{
D2D1_BRUSH_PROPERTIES brushProps;
brushProps.opacity = fillType.getOpacity();
brushProps.transform = transformToMatrix (fillType.transform);
D2D1_BITMAP_BRUSH_PROPERTIES bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP);
image = fillType.image;
D2D1_SIZE_U size;
size.width = image.getWidth();
size.height = image.getHeight();
D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties();
this->image = image.convertedToFormat (Image::ARGB);
Image::BitmapData bd (this->image, Image::BitmapData::readOnly);
bp.pixelFormat = owner.pimpl->renderingTarget->GetPixelFormat();
bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
HRESULT hr = owner.pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, bitmap.resetAndGetPointerAddress());
hr = owner.pimpl->renderingTarget->CreateBitmapBrush (bitmap, bmProps, brushProps, bitmapBrush.resetAndGetPointerAddress());
currentBrush = bitmapBrush;
}
else if (fillType.isGradient())
{
gradientStops = nullptr;
D2D1_BRUSH_PROPERTIES brushProps;
brushProps.opacity = fillType.getOpacity();
brushProps.transform = transformToMatrix (fillType.transform.followedBy (transform));
const int numColors = fillType.gradient->getNumColours();
HeapBlock<D2D1_GRADIENT_STOP> stops (numColors);
for (int i = fillType.gradient->getNumColours(); --i >= 0;)
{
stops[i].color = colourToD2D (fillType.gradient->getColour (i));
stops[i].position = (FLOAT) fillType.gradient->getColourPosition (i);
}
owner.pimpl->renderingTarget->CreateGradientStopCollection (stops.getData(), numColors, gradientStops.resetAndGetPointerAddress());
if (fillType.gradient->isRadial)
{
radialGradient = nullptr;
const Point<float> p1 = fillType.gradient->point1;
const Point<float> p2 = fillType.gradient->point2;
float r = p1.getDistanceFrom(p2);
D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES props =
D2D1::RadialGradientBrushProperties(D2D1::Point2F(p1.x, p1.y),
D2D1::Point2F(0, 0),
r, r);
owner.pimpl->renderingTarget->CreateRadialGradientBrush(props, brushProps, gradientStops, radialGradient.resetAndGetPointerAddress());
currentBrush = radialGradient;
}
else
{
linearGradient = 0;
const Point<float> p1 = fillType.gradient->point1;
const Point<float> p2 = fillType.gradient->point2;
D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES props =
D2D1::LinearGradientBrushProperties(D2D1::Point2F(p1.x, p1.y),
D2D1::Point2F(p2.x, p2.y));
owner.pimpl->renderingTarget->CreateLinearGradientBrush (props, brushProps, gradientStops, linearGradient.resetAndGetPointerAddress());
currentBrush = linearGradient;
}
}
}
}
Direct2DLowLevelGraphicsContext& owner;
AffineTransform transform;
Font font;
float fontHeightToEmSizeFactor = 1.0;
IDWriteFontFace* currentFontFace = nullptr;
ComSmartPtr<IDWriteFontFace> localFontFace;
Rectangle<int> clipRect;
bool clipsRect = false, shouldClipRect = false;
Image image;
ComSmartPtr<ID2D1Bitmap> bitmap; // xxx needs a better name - what is this for??
bool clipsBitmap = false, shouldClipBitmap = false;
ComSmartPtr<ID2D1Geometry> complexClipGeometry;
D2D1_LAYER_PARAMETERS complexClipLayerParams;
ComSmartPtr<ID2D1Layer> complexClipLayer;
bool clipsComplex = false, shouldClipComplex = false;
ComSmartPtr<ID2D1Geometry> rectListGeometry;
D2D1_LAYER_PARAMETERS rectListLayerParams;
ComSmartPtr<ID2D1Layer> rectListLayer;
bool clipsRectList = false, shouldClipRectList = false;
Image maskImage;
D2D1_LAYER_PARAMETERS imageMaskLayerParams;
ComSmartPtr<ID2D1Layer> bitmapMaskLayer;
ComSmartPtr<ID2D1Bitmap> maskBitmap;
ComSmartPtr<ID2D1BitmapBrush> bitmapMaskBrush;
ID2D1Brush* currentBrush = nullptr;
ComSmartPtr<ID2D1BitmapBrush> bitmapBrush;
ComSmartPtr<ID2D1LinearGradientBrush> linearGradient;
ComSmartPtr<ID2D1RadialGradientBrush> radialGradient;
ComSmartPtr<ID2D1GradientStopCollection> gradientStops;
FillType fillType;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedState)
};
//==============================================================================
Direct2DLowLevelGraphicsContext::Direct2DLowLevelGraphicsContext (HWND hwnd_)
: hwnd (hwnd_),
currentState (nullptr),
pimpl (new Pimpl())
{
RECT windowRect;
GetClientRect (hwnd, &windowRect);
D2D1_SIZE_U size = { (UINT32) (windowRect.right - windowRect.left), (UINT32) (windowRect.bottom - windowRect.top) };
bounds.setSize (size.width, size.height);
D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties();
D2D1_HWND_RENDER_TARGET_PROPERTIES propsHwnd = D2D1::HwndRenderTargetProperties (hwnd, size);
if (pimpl->factories->d2dFactory != nullptr)
{
HRESULT hr = pimpl->factories->d2dFactory->CreateHwndRenderTarget (props, propsHwnd, pimpl->renderingTarget.resetAndGetPointerAddress());
jassert (SUCCEEDED (hr)); ignoreUnused (hr);
hr = pimpl->renderingTarget->CreateSolidColorBrush (D2D1::ColorF::ColorF (0.0f, 0.0f, 0.0f, 1.0f), pimpl->colourBrush.resetAndGetPointerAddress());
}
}
Direct2DLowLevelGraphicsContext::~Direct2DLowLevelGraphicsContext()
{
states.clear();
}
void Direct2DLowLevelGraphicsContext::resized()
{
RECT windowRect;
GetClientRect (hwnd, &windowRect);
D2D1_SIZE_U size = { (UINT32) (windowRect.right - windowRect.left), (UINT32) (windowRect.bottom - windowRect.top) };
pimpl->renderingTarget->Resize (size);
bounds.setSize (size.width, size.height);
}
void Direct2DLowLevelGraphicsContext::clear()
{
pimpl->renderingTarget->Clear (D2D1::ColorF (D2D1::ColorF::White, 0.0f)); // xxx why white and not black?
}
void Direct2DLowLevelGraphicsContext::start()
{
pimpl->renderingTarget->BeginDraw();
saveState();
}
void Direct2DLowLevelGraphicsContext::end()
{
states.clear();
currentState = 0;
pimpl->renderingTarget->EndDraw();
pimpl->renderingTarget->CheckWindowState();
}
void Direct2DLowLevelGraphicsContext::setOrigin (Point<int> o)
{
addTransform (AffineTransform::translation ((float) o.x, (float) o.y));
}
void Direct2DLowLevelGraphicsContext::addTransform (const AffineTransform& transform)
{
currentState->transform = transform.followedBy (currentState->transform);
}
float Direct2DLowLevelGraphicsContext::getPhysicalPixelScaleFactor()
{
return currentState->transform.getScaleFactor();
}
bool Direct2DLowLevelGraphicsContext::clipToRectangle (const Rectangle<int>& r)
{
currentState->clipToRectangle (r);
return ! isClipEmpty();
}
bool Direct2DLowLevelGraphicsContext::clipToRectangleList (const RectangleList<int>& clipRegion)
{
currentState->clipToRectList (pimpl->rectListToPathGeometry (clipRegion));
return ! isClipEmpty();
}
void Direct2DLowLevelGraphicsContext::excludeClipRectangle (const Rectangle<int>&)
{
//xxx
}
void Direct2DLowLevelGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform)
{
currentState->clipToPath (pimpl->pathToPathGeometry (path, transform));
}
void Direct2DLowLevelGraphicsContext::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform)
{
currentState->clipToImage (sourceImage, transform);
}
bool Direct2DLowLevelGraphicsContext::clipRegionIntersects (const Rectangle<int>& r)
{
return currentState->clipRect.intersects (r.toFloat().transformed (currentState->transform).getSmallestIntegerContainer());
}
Rectangle<int> Direct2DLowLevelGraphicsContext::getClipBounds() const
{
// xxx could this take into account complex clip regions?
return currentState->clipRect.toFloat().transformed (currentState->transform.inverted()).getSmallestIntegerContainer();
}
bool Direct2DLowLevelGraphicsContext::isClipEmpty() const
{
return currentState->clipRect.isEmpty();
}
void Direct2DLowLevelGraphicsContext::saveState()
{
states.add (new SavedState (*this));
currentState = states.getLast();
}
void Direct2DLowLevelGraphicsContext::restoreState()
{
jassert (states.size() > 1); //you should never pop the last state!
states.removeLast (1);
currentState = states.getLast();
}
void Direct2DLowLevelGraphicsContext::beginTransparencyLayer (float /*opacity*/)
{
jassertfalse; //xxx todo
}
void Direct2DLowLevelGraphicsContext::endTransparencyLayer()
{
jassertfalse; //xxx todo
}
void Direct2DLowLevelGraphicsContext::setFill (const FillType& fillType)
{
currentState->setFill (fillType);
}
void Direct2DLowLevelGraphicsContext::setOpacity (float newOpacity)
{
currentState->setOpacity (newOpacity);
}
void Direct2DLowLevelGraphicsContext::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/)
{
}
void Direct2DLowLevelGraphicsContext::fillRect (const Rectangle<int>& r, bool /*replaceExistingContents*/)
{
fillRect (r.toFloat());
}
void Direct2DLowLevelGraphicsContext::fillRect (const Rectangle<float>& r)
{
pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform));
currentState->createBrush();
pimpl->renderingTarget->FillRectangle (rectangleToRectF (r), currentState->currentBrush);
pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
}
void Direct2DLowLevelGraphicsContext::fillRectList (const RectangleList<float>& list)
{
for (auto& r : list)
fillRect (r);
}
void Direct2DLowLevelGraphicsContext::fillPath (const Path& p, const AffineTransform& transform)
{
currentState->createBrush();
ComSmartPtr<ID2D1Geometry> geometry (pimpl->pathToPathGeometry (p, transform.followedBy (currentState->transform)));
if (pimpl->renderingTarget != nullptr)
pimpl->renderingTarget->FillGeometry (geometry, currentState->currentBrush);
}
void Direct2DLowLevelGraphicsContext::drawImage (const Image& image, const AffineTransform& transform)
{
pimpl->renderingTarget->SetTransform (transformToMatrix (transform.followedBy (currentState->transform)));
D2D1_SIZE_U size;
size.width = image.getWidth();
size.height = image.getHeight();
D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties();
Image img (image.convertedToFormat (Image::ARGB));
Image::BitmapData bd (img, Image::BitmapData::readOnly);
bp.pixelFormat = pimpl->renderingTarget->GetPixelFormat();
bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
{
ComSmartPtr<ID2D1Bitmap> tempBitmap;
pimpl->renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, tempBitmap.resetAndGetPointerAddress());
if (tempBitmap != nullptr)
pimpl->renderingTarget->DrawBitmap (tempBitmap);
}
pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
}
void Direct2DLowLevelGraphicsContext::drawLine (const Line <float>& line)
{
// xxx doesn't seem to be correctly aligned, may need nudging by 0.5 to match the software renderer's behaviour
pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform));
currentState->createBrush();
pimpl->renderingTarget->DrawLine (D2D1::Point2F (line.getStartX(), line.getStartY()),
D2D1::Point2F (line.getEndX(), line.getEndY()),
currentState->currentBrush);
pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
}
void Direct2DLowLevelGraphicsContext::setFont (const Font& newFont)
{
currentState->setFont (newFont);
}
const Font& Direct2DLowLevelGraphicsContext::getFont()
{
return currentState->font;
}
void Direct2DLowLevelGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& transform)
{
currentState->createBrush();
currentState->createFont();
float hScale = currentState->font.getHorizontalScale();
pimpl->renderingTarget->SetTransform (transformToMatrix (AffineTransform::scale (hScale, 1.0f)
.followedBy (transform)
.followedBy (currentState->transform)));
const UINT16 glyphIndices = (UINT16) glyphNumber;
const FLOAT glyphAdvances = 0;
DWRITE_GLYPH_OFFSET offset;
offset.advanceOffset = 0;
offset.ascenderOffset = 0;
DWRITE_GLYPH_RUN glyphRun;
glyphRun.fontFace = currentState->currentFontFace;
glyphRun.fontEmSize = (FLOAT) (currentState->font.getHeight() * currentState->fontHeightToEmSizeFactor);
glyphRun.glyphCount = 1;
glyphRun.glyphIndices = &glyphIndices;
glyphRun.glyphAdvances = &glyphAdvances;
glyphRun.glyphOffsets = &offset;
glyphRun.isSideways = FALSE;
glyphRun.bidiLevel = 0;
pimpl->renderingTarget->DrawGlyphRun (D2D1::Point2F (0, 0), &glyphRun, currentState->currentBrush);
pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
}
bool Direct2DLowLevelGraphicsContext::drawTextLayout (const AttributedString& text, const Rectangle<float>& area)
{
pimpl->renderingTarget->SetTransform (transformToMatrix (currentState->transform));
DirectWriteTypeLayout::drawToD2DContext (text, area,
*(pimpl->renderingTarget),
*(pimpl->factories->directWriteFactory),
*(pimpl->factories->systemFonts));
pimpl->renderingTarget->SetTransform (D2D1::IdentityMatrix());
return true;
}
} // namespace juce

View File

@ -0,0 +1,106 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#ifndef _WINDEF_
class HWND__; // Forward or never
typedef HWND__* HWND;
#endif
class Direct2DLowLevelGraphicsContext : public LowLevelGraphicsContext
{
public:
Direct2DLowLevelGraphicsContext (HWND);
~Direct2DLowLevelGraphicsContext();
//==============================================================================
bool isVectorDevice() const override { return false; }
void setOrigin (Point<int>) override;
void addTransform (const AffineTransform&) override;
float getPhysicalPixelScaleFactor() override;
bool clipToRectangle (const Rectangle<int>&) override;
bool clipToRectangleList (const RectangleList<int>&) override;
void excludeClipRectangle (const Rectangle<int>&) override;
void clipToPath (const Path&, const AffineTransform&) override;
void clipToImageAlpha (const Image&, const AffineTransform&) override;
bool clipRegionIntersects (const Rectangle<int>&) override;
Rectangle<int> getClipBounds() const override;
bool isClipEmpty() const override;
//==============================================================================
void saveState() override;
void restoreState() override;
void beginTransparencyLayer (float opacity) override;
void endTransparencyLayer() override;
//==============================================================================
void setFill (const FillType&) override;
void setOpacity (float) override;
void setInterpolationQuality (Graphics::ResamplingQuality) override;
//==============================================================================
void fillRect (const Rectangle<int>&, bool replaceExistingContents) override;
void fillRect (const Rectangle<float>&) override;
void fillRectList (const RectangleList<float>&) override;
void fillPath (const Path&, const AffineTransform&) override;
void drawImage (const Image& sourceImage, const AffineTransform&) override;
//==============================================================================
void drawLine (const Line<float>&) override;
void setFont (const Font&) override;
const Font& getFont() override;
void drawGlyph (int glyphNumber, const AffineTransform&) override;
bool drawTextLayout (const AttributedString&, const Rectangle<float>&) override;
void resized();
void clear();
void start();
void end();
//==============================================================================
private:
struct SavedState;
HWND hwnd;
SavedState* currentState;
OwnedArray<SavedState> states;
Rectangle<int> bounds;
struct Pimpl;
friend struct Pimpl;
friend struct ContainerDeletePolicy<Pimpl>;
std::unique_ptr<Pimpl> pimpl;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DLowLevelGraphicsContext)
};
} // namespace juce

View File

@ -0,0 +1,460 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#if JUCE_USE_DIRECTWRITE
namespace DirectWriteTypeLayout
{
class CustomDirectWriteTextRenderer : public ComBaseClassHelper<IDWriteTextRenderer>
{
public:
CustomDirectWriteTextRenderer (IDWriteFontCollection& fonts, const AttributedString& as)
: ComBaseClassHelper<IDWriteTextRenderer> (0),
attributedString (as),
fontCollection (fonts),
currentLine (-1),
lastOriginY (-10000.0f)
{
}
JUCE_COMRESULT QueryInterface (REFIID refId, void** result) override
{
if (refId == __uuidof (IDWritePixelSnapping))
return castToType<IDWritePixelSnapping> (result);
return ComBaseClassHelper<IDWriteTextRenderer>::QueryInterface (refId, result);
}
JUCE_COMRESULT IsPixelSnappingDisabled (void* /*clientDrawingContext*/, BOOL* isDisabled) override
{
*isDisabled = FALSE;
return S_OK;
}
JUCE_COMRESULT GetCurrentTransform (void*, DWRITE_MATRIX* matrix) override
{
matrix->m11 = 1.0f; matrix->m12 = 0.0f;
matrix->m21 = 0.0f; matrix->m22 = 1.0f;
matrix->dx = 0.0f; matrix->dy = 0.0f;
return S_OK;
}
JUCE_COMRESULT GetPixelsPerDip (void*, FLOAT* pixelsPerDip) override
{
*pixelsPerDip = 1.0f;
return S_OK;
}
JUCE_COMRESULT DrawUnderline (void*, FLOAT, FLOAT, DWRITE_UNDERLINE const*, IUnknown*) override
{
return E_NOTIMPL;
}
JUCE_COMRESULT DrawStrikethrough (void*, FLOAT, FLOAT, DWRITE_STRIKETHROUGH const*, IUnknown*) override
{
return E_NOTIMPL;
}
JUCE_COMRESULT DrawInlineObject (void*, FLOAT, FLOAT, IDWriteInlineObject*, BOOL, BOOL, IUnknown*) override
{
return E_NOTIMPL;
}
JUCE_COMRESULT DrawGlyphRun (void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE,
DWRITE_GLYPH_RUN const* glyphRun, DWRITE_GLYPH_RUN_DESCRIPTION const* runDescription,
IUnknown* clientDrawingEffect) override
{
TextLayout* const layout = static_cast<TextLayout*> (clientDrawingContext);
if (! (baselineOriginY >= -1.0e10f && baselineOriginY <= 1.0e10f))
baselineOriginY = 0; // DirectWrite sometimes sends NaNs in this parameter
if (baselineOriginY != lastOriginY)
{
lastOriginY = baselineOriginY;
++currentLine;
if (currentLine >= layout->getNumLines())
{
jassert (currentLine == layout->getNumLines());
TextLayout::Line* const line = new TextLayout::Line();
layout->addLine (line);
line->lineOrigin = Point<float> (baselineOriginX, baselineOriginY);
}
}
TextLayout::Line& glyphLine = layout->getLine (currentLine);
DWRITE_FONT_METRICS dwFontMetrics;
glyphRun->fontFace->GetMetrics (&dwFontMetrics);
glyphLine.ascent = jmax (glyphLine.ascent, scaledFontSize (dwFontMetrics.ascent, dwFontMetrics, *glyphRun));
glyphLine.descent = jmax (glyphLine.descent, scaledFontSize (dwFontMetrics.descent, dwFontMetrics, *glyphRun));
TextLayout::Run* const glyphRunLayout = new TextLayout::Run (Range<int> (runDescription->textPosition,
runDescription->textPosition + runDescription->stringLength),
glyphRun->glyphCount);
glyphLine.runs.add (glyphRunLayout);
glyphRun->fontFace->GetMetrics (&dwFontMetrics);
const float totalHeight = std::abs ((float) dwFontMetrics.ascent) + std::abs ((float) dwFontMetrics.descent);
const float fontHeightToEmSizeFactor = (float) dwFontMetrics.designUnitsPerEm / totalHeight;
glyphRunLayout->font = getFontForRun (*glyphRun, glyphRun->fontEmSize / fontHeightToEmSizeFactor);
glyphRunLayout->colour = getColourOf (static_cast<ID2D1SolidColorBrush*> (clientDrawingEffect));
const Point<float> lineOrigin (layout->getLine (currentLine).lineOrigin);
float x = baselineOriginX - lineOrigin.x;
const float extraKerning = glyphRunLayout->font.getExtraKerningFactor()
* glyphRunLayout->font.getHeight();
for (UINT32 i = 0; i < glyphRun->glyphCount; ++i)
{
const float advance = glyphRun->glyphAdvances[i];
if ((glyphRun->bidiLevel & 1) != 0)
x -= advance + extraKerning; // RTL text
glyphRunLayout->glyphs.add (TextLayout::Glyph (glyphRun->glyphIndices[i],
Point<float> (x, baselineOriginY - lineOrigin.y),
advance));
if ((glyphRun->bidiLevel & 1) == 0)
x += advance + extraKerning; // LTR text
}
return S_OK;
}
private:
const AttributedString& attributedString;
IDWriteFontCollection& fontCollection;
int currentLine;
float lastOriginY;
static float scaledFontSize (int n, const DWRITE_FONT_METRICS& metrics, const DWRITE_GLYPH_RUN& glyphRun) noexcept
{
return (std::abs ((float) n) / (float) metrics.designUnitsPerEm) * glyphRun.fontEmSize;
}
static Colour getColourOf (ID2D1SolidColorBrush* d2dBrush) noexcept
{
if (d2dBrush == nullptr)
return Colours::black;
const D2D1_COLOR_F colour (d2dBrush->GetColor());
return Colour::fromFloatRGBA (colour.r, colour.g, colour.b, colour.a);
}
Font getFontForRun (const DWRITE_GLYPH_RUN& glyphRun, float fontHeight)
{
for (int i = 0; i < attributedString.getNumAttributes(); ++i)
{
const Font& font = attributedString.getAttribute(i).font;
if (WindowsDirectWriteTypeface* wt = dynamic_cast<WindowsDirectWriteTypeface*> (font.getTypeface()))
if (wt->getIDWriteFontFace() == glyphRun.fontFace)
return font.withHeight (fontHeight);
}
ComSmartPtr<IDWriteFont> dwFont;
HRESULT hr = fontCollection.GetFontFromFontFace (glyphRun.fontFace, dwFont.resetAndGetPointerAddress());
jassert (dwFont != nullptr);
ComSmartPtr<IDWriteFontFamily> dwFontFamily;
hr = dwFont->GetFontFamily (dwFontFamily.resetAndGetPointerAddress());
return Font (getFontFamilyName (dwFontFamily), getFontFaceName (dwFont), fontHeight);
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomDirectWriteTextRenderer)
};
//==============================================================================
static float getFontHeightToEmSizeFactor (IDWriteFont& dwFont)
{
ComSmartPtr<IDWriteFontFace> dwFontFace;
dwFont.CreateFontFace (dwFontFace.resetAndGetPointerAddress());
if (dwFontFace == nullptr)
return 1.0f;
DWRITE_FONT_METRICS dwFontMetrics;
dwFontFace->GetMetrics (&dwFontMetrics);
const float totalHeight = (float) (std::abs (dwFontMetrics.ascent) + std::abs (dwFontMetrics.descent));
return dwFontMetrics.designUnitsPerEm / totalHeight;
}
void setTextFormatProperties (const AttributedString& text, IDWriteTextFormat& format)
{
DWRITE_TEXT_ALIGNMENT alignment = DWRITE_TEXT_ALIGNMENT_LEADING;
DWRITE_WORD_WRAPPING wrapType = DWRITE_WORD_WRAPPING_WRAP;
switch (text.getJustification().getOnlyHorizontalFlags())
{
case Justification::left: break;
case Justification::right: alignment = DWRITE_TEXT_ALIGNMENT_TRAILING; break;
case Justification::horizontallyCentred: alignment = DWRITE_TEXT_ALIGNMENT_CENTER; break;
case Justification::horizontallyJustified: break; // DirectWrite cannot justify text, default to left alignment
default: jassertfalse; break; // Illegal justification flags
}
switch (text.getWordWrap())
{
case AttributedString::none: wrapType = DWRITE_WORD_WRAPPING_NO_WRAP; break;
case AttributedString::byWord: break;
case AttributedString::byChar: break; // DirectWrite doesn't support wrapping by character, default to word-wrap
default: jassertfalse; break; // Illegal flags!
}
// DirectWrite does not automatically set reading direction
// This must be set correctly and manually when using RTL Scripts (Hebrew, Arabic)
if (text.getReadingDirection() == AttributedString::rightToLeft)
{
format.SetReadingDirection (DWRITE_READING_DIRECTION_RIGHT_TO_LEFT);
switch (text.getJustification().getOnlyHorizontalFlags())
{
case Justification::left: alignment = DWRITE_TEXT_ALIGNMENT_TRAILING; break;
case Justification::right: alignment = DWRITE_TEXT_ALIGNMENT_LEADING; break;
default: break;
}
}
format.SetTextAlignment (alignment);
format.SetWordWrapping (wrapType);
}
void addAttributedRange (const AttributedString::Attribute& attr, IDWriteTextLayout& textLayout,
const int textLen, ID2D1RenderTarget& renderTarget, IDWriteFontCollection& fontCollection)
{
DWRITE_TEXT_RANGE range;
range.startPosition = attr.range.getStart();
range.length = jmin (attr.range.getLength(), textLen - attr.range.getStart());
{
const String familyName (FontStyleHelpers::getConcreteFamilyName (attr.font));
BOOL fontFound = false;
uint32 fontIndex;
fontCollection.FindFamilyName (familyName.toWideCharPointer(), &fontIndex, &fontFound);
if (! fontFound)
fontIndex = 0;
ComSmartPtr<IDWriteFontFamily> fontFamily;
HRESULT hr = fontCollection.GetFontFamily (fontIndex, fontFamily.resetAndGetPointerAddress());
ComSmartPtr<IDWriteFont> dwFont;
uint32 fontFacesCount = 0;
fontFacesCount = fontFamily->GetFontCount();
for (int i = fontFacesCount; --i >= 0;)
{
hr = fontFamily->GetFont (i, dwFont.resetAndGetPointerAddress());
if (attr.font.getTypefaceStyle() == getFontFaceName (dwFont))
break;
}
textLayout.SetFontFamilyName (familyName.toWideCharPointer(), range);
textLayout.SetFontWeight (dwFont->GetWeight(), range);
textLayout.SetFontStretch (dwFont->GetStretch(), range);
textLayout.SetFontStyle (dwFont->GetStyle(), range);
const float fontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (*dwFont);
textLayout.SetFontSize (attr.font.getHeight() * fontHeightToEmSizeFactor, range);
}
{
const Colour col (attr.colour);
ComSmartPtr<ID2D1SolidColorBrush> d2dBrush;
renderTarget.CreateSolidColorBrush (D2D1::ColorF (col.getFloatRed(),
col.getFloatGreen(),
col.getFloatBlue(),
col.getFloatAlpha()),
d2dBrush.resetAndGetPointerAddress());
// We need to call SetDrawingEffect with a legimate brush to get DirectWrite to break text based on colours
textLayout.SetDrawingEffect (d2dBrush, range);
}
}
bool setupLayout (const AttributedString& text, const float maxWidth, const float maxHeight,
ID2D1RenderTarget& renderTarget, IDWriteFactory& directWriteFactory,
IDWriteFontCollection& fontCollection, ComSmartPtr<IDWriteTextLayout>& textLayout)
{
// To add color to text, we need to create a D2D render target
// Since we are not actually rendering to a D2D context we create a temporary GDI render target
Font defaultFont;
BOOL fontFound = false;
uint32 fontIndex;
fontCollection.FindFamilyName (defaultFont.getTypeface()->getName().toWideCharPointer(), &fontIndex, &fontFound);
if (! fontFound)
fontIndex = 0;
ComSmartPtr<IDWriteFontFamily> dwFontFamily;
HRESULT hr = fontCollection.GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress());
ComSmartPtr<IDWriteFont> dwFont;
hr = dwFontFamily->GetFirstMatchingFont (DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL,
dwFont.resetAndGetPointerAddress());
jassert (dwFont != nullptr);
const float defaultFontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (*dwFont);
ComSmartPtr<IDWriteTextFormat> dwTextFormat;
hr = directWriteFactory.CreateTextFormat (defaultFont.getTypefaceName().toWideCharPointer(), &fontCollection,
DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
defaultFont.getHeight() * defaultFontHeightToEmSizeFactor,
L"en-us", dwTextFormat.resetAndGetPointerAddress());
setTextFormatProperties (text, *dwTextFormat);
{
DWRITE_TRIMMING trimming = { DWRITE_TRIMMING_GRANULARITY_CHARACTER, 0, 0 };
ComSmartPtr<IDWriteInlineObject> trimmingSign;
hr = directWriteFactory.CreateEllipsisTrimmingSign (dwTextFormat, trimmingSign.resetAndGetPointerAddress());
hr = dwTextFormat->SetTrimming (&trimming, trimmingSign);
}
const int textLen = text.getText().length();
hr = directWriteFactory.CreateTextLayout (text.getText().toWideCharPointer(), textLen, dwTextFormat,
maxWidth, maxHeight, textLayout.resetAndGetPointerAddress());
if (FAILED (hr) || textLayout == nullptr)
return false;
const int numAttributes = text.getNumAttributes();
for (int i = 0; i < numAttributes; ++i)
addAttributedRange (text.getAttribute (i), *textLayout, textLen, renderTarget, fontCollection);
return true;
}
void createLayout (TextLayout& layout, const AttributedString& text,
IDWriteFactory& directWriteFactory,
IDWriteFontCollection& fontCollection,
ID2D1DCRenderTarget& renderTarget)
{
ComSmartPtr<IDWriteTextLayout> dwTextLayout;
if (! setupLayout (text, layout.getWidth(), layout.getHeight(), renderTarget,
directWriteFactory, fontCollection, dwTextLayout))
return;
UINT32 actualLineCount = 0;
HRESULT hr = dwTextLayout->GetLineMetrics (nullptr, 0, &actualLineCount);
layout.ensureStorageAllocated (actualLineCount);
{
ComSmartPtr<CustomDirectWriteTextRenderer> textRenderer (new CustomDirectWriteTextRenderer (fontCollection, text));
hr = dwTextLayout->Draw (&layout, textRenderer, 0, 0);
}
HeapBlock<DWRITE_LINE_METRICS> dwLineMetrics (actualLineCount);
hr = dwTextLayout->GetLineMetrics (dwLineMetrics, actualLineCount, &actualLineCount);
int lastLocation = 0;
const int numLines = jmin ((int) actualLineCount, layout.getNumLines());
float yAdjustment = 0;
const float extraLineSpacing = text.getLineSpacing();
for (int i = 0; i < numLines; ++i)
{
TextLayout::Line& line = layout.getLine (i);
line.stringRange = Range<int> (lastLocation, (int) lastLocation + dwLineMetrics[i].length);
line.lineOrigin.y += yAdjustment;
yAdjustment += extraLineSpacing;
lastLocation += dwLineMetrics[i].length;
}
}
void drawToD2DContext (const AttributedString& text, const Rectangle<float>& area, ID2D1RenderTarget& renderTarget,
IDWriteFactory& directWriteFactory, IDWriteFontCollection& fontCollection)
{
ComSmartPtr<IDWriteTextLayout> dwTextLayout;
if (setupLayout (text, area.getWidth(), area.getHeight(), renderTarget,
directWriteFactory, fontCollection, dwTextLayout))
{
ComSmartPtr<ID2D1SolidColorBrush> d2dBrush;
renderTarget.CreateSolidColorBrush (D2D1::ColorF (0.0f, 0.0f, 0.0f, 1.0f),
d2dBrush.resetAndGetPointerAddress());
renderTarget.DrawTextLayout (D2D1::Point2F ((float) area.getX(), (float) area.getY()),
dwTextLayout, d2dBrush, D2D1_DRAW_TEXT_OPTIONS_CLIP);
}
}
}
static bool canAllTypefacesBeUsedInLayout (const AttributedString& text)
{
const int numCharacterAttributes = text.getNumAttributes();
for (int i = 0; i < numCharacterAttributes; ++i)
if (dynamic_cast<WindowsDirectWriteTypeface*> (text.getAttribute(i).font.getTypeface()) == nullptr)
return false;
return true;
}
#endif
bool TextLayout::createNativeLayout (const AttributedString& text)
{
#if JUCE_USE_DIRECTWRITE
if (! canAllTypefacesBeUsedInLayout (text))
return false;
SharedResourcePointer<Direct2DFactories> factories;
if (factories->d2dFactory != nullptr && factories->systemFonts != nullptr)
{
DirectWriteTypeLayout::createLayout (*this, text,
*factories->directWriteFactory,
*factories->systemFonts,
*factories->directWriteRenderTarget);
return true;
}
#else
ignoreUnused (text);
#endif
return false;
}
} // namespace juce

View File

@ -0,0 +1,327 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#if JUCE_USE_DIRECTWRITE
namespace
{
static String getLocalisedName (IDWriteLocalizedStrings* names)
{
jassert (names != nullptr);
uint32 index = 0;
BOOL exists = false;
HRESULT hr = names->FindLocaleName (L"en-us", &index, &exists);
if (! exists)
index = 0;
uint32 length = 0;
hr = names->GetStringLength (index, &length);
HeapBlock<wchar_t> name (length + 1);
hr = names->GetString (index, name, length + 1);
return static_cast<const wchar_t*> (name);
}
static String getFontFamilyName (IDWriteFontFamily* family)
{
jassert (family != nullptr);
ComSmartPtr<IDWriteLocalizedStrings> familyNames;
HRESULT hr = family->GetFamilyNames (familyNames.resetAndGetPointerAddress());
jassert (SUCCEEDED (hr)); ignoreUnused (hr);
return getLocalisedName (familyNames);
}
static String getFontFaceName (IDWriteFont* font)
{
jassert (font != nullptr);
ComSmartPtr<IDWriteLocalizedStrings> faceNames;
HRESULT hr = font->GetFaceNames (faceNames.resetAndGetPointerAddress());
jassert (SUCCEEDED (hr)); ignoreUnused (hr);
return getLocalisedName (faceNames);
}
inline Point<float> convertPoint (D2D1_POINT_2F p) noexcept { return Point<float> ((float) p.x, (float) p.y); }
}
class Direct2DFactories
{
public:
Direct2DFactories()
{
if (direct2dDll.open ("d2d1.dll"))
{
JUCE_LOAD_WINAPI_FUNCTION (direct2dDll, D2D1CreateFactory, d2d1CreateFactory,
HRESULT, (D2D1_FACTORY_TYPE, REFIID, D2D1_FACTORY_OPTIONS*, void**))
if (d2d1CreateFactory != nullptr)
{
D2D1_FACTORY_OPTIONS options;
options.debugLevel = D2D1_DEBUG_LEVEL_NONE;
d2d1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof (ID2D1Factory), &options,
(void**) d2dFactory.resetAndGetPointerAddress());
}
}
if (directWriteDll.open ("DWrite.dll"))
{
JUCE_LOAD_WINAPI_FUNCTION (directWriteDll, DWriteCreateFactory, dWriteCreateFactory,
HRESULT, (DWRITE_FACTORY_TYPE, REFIID, IUnknown**))
if (dWriteCreateFactory != nullptr)
{
dWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, __uuidof (IDWriteFactory),
(IUnknown**) directWriteFactory.resetAndGetPointerAddress());
if (directWriteFactory != nullptr)
directWriteFactory->GetSystemFontCollection (systemFonts.resetAndGetPointerAddress());
}
if (d2dFactory != nullptr)
{
D2D1_RENDER_TARGET_PROPERTIES d2dRTProp = D2D1::RenderTargetProperties (D2D1_RENDER_TARGET_TYPE_SOFTWARE,
D2D1::PixelFormat (DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_IGNORE),
0, 0,
D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE,
D2D1_FEATURE_LEVEL_DEFAULT);
d2dFactory->CreateDCRenderTarget (&d2dRTProp, directWriteRenderTarget.resetAndGetPointerAddress());
}
}
}
~Direct2DFactories()
{
d2dFactory = nullptr; // (need to make sure these are released before deleting the DynamicLibrary objects)
directWriteFactory = nullptr;
systemFonts = nullptr;
directWriteRenderTarget = nullptr;
}
ComSmartPtr<ID2D1Factory> d2dFactory;
ComSmartPtr<IDWriteFactory> directWriteFactory;
ComSmartPtr<IDWriteFontCollection> systemFonts;
ComSmartPtr<ID2D1DCRenderTarget> directWriteRenderTarget;
private:
DynamicLibrary direct2dDll, directWriteDll;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DFactories)
};
//==============================================================================
class WindowsDirectWriteTypeface : public Typeface
{
public:
WindowsDirectWriteTypeface (const Font& font, IDWriteFontCollection* fontCollection)
: Typeface (font.getTypefaceName(), font.getTypefaceStyle()),
unitsToHeightScaleFactor (1.0f), heightToPointsFactor (1.0f), ascent (0.0f)
{
jassert (fontCollection != nullptr);
BOOL fontFound = false;
uint32 fontIndex = 0;
HRESULT hr = fontCollection->FindFamilyName (font.getTypefaceName().toWideCharPointer(), &fontIndex, &fontFound);
if (! fontFound)
fontIndex = 0;
// Get the font family using the search results
// Fonts like: Times New Roman, Times New Roman Bold, Times New Roman Italic are all in the same font family
ComSmartPtr<IDWriteFontFamily> dwFontFamily;
hr = fontCollection->GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress());
// Get a specific font in the font family using typeface style
{
ComSmartPtr<IDWriteFont> dwFont;
for (int i = (int) dwFontFamily->GetFontCount(); --i >= 0;)
{
hr = dwFontFamily->GetFont (i, dwFont.resetAndGetPointerAddress());
if (i == 0)
break;
ComSmartPtr<IDWriteLocalizedStrings> faceNames;
hr = dwFont->GetFaceNames (faceNames.resetAndGetPointerAddress());
if (font.getTypefaceStyle() == getLocalisedName (faceNames))
break;
}
jassert (dwFont != nullptr);
hr = dwFont->CreateFontFace (dwFontFace.resetAndGetPointerAddress());
}
if (dwFontFace != nullptr)
{
DWRITE_FONT_METRICS dwFontMetrics;
dwFontFace->GetMetrics (&dwFontMetrics);
// All Font Metrics are in design units so we need to get designUnitsPerEm value
// to get the metrics into Em/Design Independent Pixels
designUnitsPerEm = dwFontMetrics.designUnitsPerEm;
ascent = std::abs ((float) dwFontMetrics.ascent);
const float totalSize = ascent + std::abs ((float) dwFontMetrics.descent);
ascent /= totalSize;
unitsToHeightScaleFactor = designUnitsPerEm / totalSize;
HDC tempDC = GetDC (0);
float dpi = (GetDeviceCaps (tempDC, LOGPIXELSX) + GetDeviceCaps (tempDC, LOGPIXELSY)) / 2.0f;
heightToPointsFactor = (dpi / GetDeviceCaps (tempDC, LOGPIXELSY)) * unitsToHeightScaleFactor;
ReleaseDC (0, tempDC);
const float pathAscent = (1024.0f * dwFontMetrics.ascent) / designUnitsPerEm;
const float pathDescent = (1024.0f * dwFontMetrics.descent) / designUnitsPerEm;
const float pathScale = 1.0f / (std::abs (pathAscent) + std::abs (pathDescent));
pathTransform = AffineTransform::scale (pathScale);
}
}
bool loadedOk() const noexcept { return dwFontFace != nullptr; }
float getAscent() const { return ascent; }
float getDescent() const { return 1.0f - ascent; }
float getHeightToPointsFactor() const { return heightToPointsFactor; }
float getStringWidth (const String& text)
{
const CharPointer_UTF32 textUTF32 (text.toUTF32());
const size_t len = textUTF32.length();
HeapBlock<UINT16> glyphIndices (len);
dwFontFace->GetGlyphIndices (textUTF32, (UINT32) len, glyphIndices);
HeapBlock<DWRITE_GLYPH_METRICS> dwGlyphMetrics (len);
dwFontFace->GetDesignGlyphMetrics (glyphIndices, (UINT32) len, dwGlyphMetrics, false);
float x = 0;
for (size_t i = 0; i < len; ++i)
x += (float) dwGlyphMetrics[i].advanceWidth / designUnitsPerEm;
return x * unitsToHeightScaleFactor;
}
void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets)
{
xOffsets.add (0);
const CharPointer_UTF32 textUTF32 (text.toUTF32());
const size_t len = textUTF32.length();
HeapBlock<UINT16> glyphIndices (len);
dwFontFace->GetGlyphIndices (textUTF32, (UINT32) len, glyphIndices);
HeapBlock<DWRITE_GLYPH_METRICS> dwGlyphMetrics (len);
dwFontFace->GetDesignGlyphMetrics (glyphIndices, (UINT32) len, dwGlyphMetrics, false);
float x = 0;
for (size_t i = 0; i < len; ++i)
{
x += (float) dwGlyphMetrics[i].advanceWidth / designUnitsPerEm;
xOffsets.add (x * unitsToHeightScaleFactor);
resultGlyphs.add (glyphIndices[i]);
}
}
bool getOutlineForGlyph (int glyphNumber, Path& path)
{
jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty
UINT16 glyphIndex = (UINT16) glyphNumber;
ComSmartPtr<PathGeometrySink> pathGeometrySink (new PathGeometrySink());
dwFontFace->GetGlyphRunOutline (1024.0f, &glyphIndex, nullptr, nullptr, 1, false, false, pathGeometrySink);
path = pathGeometrySink->path;
if (! pathTransform.isIdentity())
path.applyTransform (pathTransform);
return true;
}
IDWriteFontFace* getIDWriteFontFace() const noexcept { return dwFontFace; }
float getUnitsToHeightScaleFactor() const noexcept { return unitsToHeightScaleFactor; }
private:
SharedResourcePointer<Direct2DFactories> factories;
ComSmartPtr<IDWriteFontFace> dwFontFace;
float unitsToHeightScaleFactor, heightToPointsFactor, ascent;
int designUnitsPerEm;
AffineTransform pathTransform;
struct PathGeometrySink : public ComBaseClassHelper<IDWriteGeometrySink>
{
PathGeometrySink() : ComBaseClassHelper<IDWriteGeometrySink> (0) {}
void __stdcall AddBeziers (const D2D1_BEZIER_SEGMENT* beziers, UINT beziersCount) override
{
for (UINT i = 0; i < beziersCount; ++i)
path.cubicTo (convertPoint (beziers[i].point1),
convertPoint (beziers[i].point2),
convertPoint (beziers[i].point3));
}
void __stdcall AddLines (const D2D1_POINT_2F* points, UINT pointsCount) override
{
for (UINT i = 0; i < pointsCount; ++i)
path.lineTo (convertPoint (points[i]));
}
void __stdcall BeginFigure (D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN) override
{
path.startNewSubPath (convertPoint (startPoint));
}
void __stdcall EndFigure (D2D1_FIGURE_END figureEnd) override
{
if (figureEnd == D2D1_FIGURE_END_CLOSED)
path.closeSubPath();
}
void __stdcall SetFillMode (D2D1_FILL_MODE fillMode) override
{
path.setUsingNonZeroWinding (fillMode == D2D1_FILL_MODE_WINDING);
}
void __stdcall SetSegmentFlags (D2D1_PATH_SEGMENT) override {}
JUCE_COMRESULT Close() override { return S_OK; }
Path path;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PathGeometrySink)
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsDirectWriteTypeface)
};
#endif
} // namespace juce

View File

@ -0,0 +1,649 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/* This is some quick-and-dirty code to extract the typeface name from a lump of TTF file data.
It's needed because although win32 will happily load a TTF file from in-memory data, it won't
tell you the name of the damned font that it just loaded.. and in order to actually use the font,
you need to know its name!! Anyway, this awful hack seems to work for most fonts.
*/
namespace TTFNameExtractor
{
struct OffsetTable
{
uint32 version;
uint16 numTables, searchRange, entrySelector, rangeShift;
};
struct TableDirectory
{
char tag[4];
uint32 checkSum, offset, length;
};
struct NamingTable
{
uint16 formatSelector;
uint16 numberOfNameRecords;
uint16 offsetStartOfStringStorage;
};
struct NameRecord
{
uint16 platformID, encodingID, languageID;
uint16 nameID, stringLength, offsetFromStorageArea;
};
static String parseNameRecord (MemoryInputStream& input, const NameRecord& nameRecord,
const int64 directoryOffset, const int64 offsetOfStringStorage)
{
String result;
const int64 oldPos = input.getPosition();
input.setPosition (directoryOffset + offsetOfStringStorage + ByteOrder::swapIfLittleEndian (nameRecord.offsetFromStorageArea));
const int stringLength = (int) ByteOrder::swapIfLittleEndian (nameRecord.stringLength);
const int platformID = ByteOrder::swapIfLittleEndian (nameRecord.platformID);
if (platformID == 0 || platformID == 3)
{
const int numChars = stringLength / 2 + 1;
HeapBlock<uint16> buffer;
buffer.calloc (numChars + 1);
input.read (buffer, stringLength);
for (int i = 0; i < numChars; ++i)
buffer[i] = ByteOrder::swapIfLittleEndian (buffer[i]);
static_assert (sizeof (CharPointer_UTF16::CharType) == sizeof (uint16), "Sanity check UTF-16 type");
result = CharPointer_UTF16 ((CharPointer_UTF16::CharType*) buffer.getData());
}
else
{
HeapBlock<char> buffer;
buffer.calloc (stringLength + 1);
input.read (buffer, stringLength);
result = CharPointer_UTF8 (buffer.getData());
}
input.setPosition (oldPos);
return result;
}
static String parseNameTable (MemoryInputStream& input, int64 directoryOffset)
{
input.setPosition (directoryOffset);
NamingTable namingTable = { 0 };
input.read (&namingTable, sizeof (namingTable));
for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (namingTable.numberOfNameRecords); ++i)
{
NameRecord nameRecord = { 0 };
input.read (&nameRecord, sizeof (nameRecord));
if (ByteOrder::swapIfLittleEndian (nameRecord.nameID) == 4)
{
const String result (parseNameRecord (input, nameRecord, directoryOffset,
ByteOrder::swapIfLittleEndian (namingTable.offsetStartOfStringStorage)));
if (result.isNotEmpty())
return result;
}
}
return {};
}
static String getTypefaceNameFromFile (MemoryInputStream& input)
{
OffsetTable offsetTable = { 0 };
input.read (&offsetTable, sizeof (offsetTable));
for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (offsetTable.numTables); ++i)
{
TableDirectory tableDirectory;
zerostruct (tableDirectory);
input.read (&tableDirectory, sizeof (tableDirectory));
if (String (tableDirectory.tag, sizeof (tableDirectory.tag)).equalsIgnoreCase ("name"))
return parseNameTable (input, ByteOrder::swapIfLittleEndian (tableDirectory.offset));
}
return {};
}
}
namespace FontEnumerators
{
static int CALLBACK fontEnum2 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam)
{
if (lpelfe != nullptr && (type & RASTER_FONTTYPE) == 0)
{
const String fontName (lpelfe->elfLogFont.lfFaceName);
((StringArray*) lParam)->addIfNotAlreadyThere (fontName.removeCharacters ("@"));
}
return 1;
}
static int CALLBACK fontEnum1 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam)
{
if (lpelfe != nullptr && (type & RASTER_FONTTYPE) == 0)
{
LOGFONTW lf = { 0 };
lf.lfWeight = FW_DONTCARE;
lf.lfOutPrecision = OUT_OUTLINE_PRECIS;
lf.lfQuality = DEFAULT_QUALITY;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
lf.lfPitchAndFamily = FF_DONTCARE;
const String fontName (lpelfe->elfLogFont.lfFaceName);
fontName.copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName));
HDC dc = CreateCompatibleDC (0);
EnumFontFamiliesEx (dc, &lf,
(FONTENUMPROCW) &fontEnum2,
lParam, 0);
DeleteDC (dc);
}
return 1;
}
}
StringArray Font::findAllTypefaceNames()
{
StringArray results;
#if JUCE_USE_DIRECTWRITE
SharedResourcePointer<Direct2DFactories> factories;
if (factories->systemFonts != nullptr)
{
ComSmartPtr<IDWriteFontFamily> fontFamily;
uint32 fontFamilyCount = 0;
fontFamilyCount = factories->systemFonts->GetFontFamilyCount();
for (uint32 i = 0; i < fontFamilyCount; ++i)
{
HRESULT hr = factories->systemFonts->GetFontFamily (i, fontFamily.resetAndGetPointerAddress());
if (SUCCEEDED (hr))
results.addIfNotAlreadyThere (getFontFamilyName (fontFamily));
}
}
else
#endif
{
HDC dc = CreateCompatibleDC (0);
{
LOGFONTW lf = { 0 };
lf.lfWeight = FW_DONTCARE;
lf.lfOutPrecision = OUT_OUTLINE_PRECIS;
lf.lfQuality = DEFAULT_QUALITY;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
lf.lfPitchAndFamily = FF_DONTCARE;
EnumFontFamiliesEx (dc, &lf,
(FONTENUMPROCW) &FontEnumerators::fontEnum1,
(LPARAM) &results, 0);
}
DeleteDC (dc);
}
results.sort (true);
return results;
}
StringArray Font::findAllTypefaceStyles (const String& family)
{
if (FontStyleHelpers::isPlaceholderFamilyName (family))
return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));
StringArray results;
#if JUCE_USE_DIRECTWRITE
SharedResourcePointer<Direct2DFactories> factories;
if (factories->systemFonts != nullptr)
{
BOOL fontFound = false;
uint32 fontIndex = 0;
HRESULT hr = factories->systemFonts->FindFamilyName (family.toWideCharPointer(), &fontIndex, &fontFound);
if (! fontFound)
fontIndex = 0;
// Get the font family using the search results
// Fonts like: Times New Roman, Times New Roman Bold, Times New Roman Italic are all in the same font family
ComSmartPtr<IDWriteFontFamily> fontFamily;
hr = factories->systemFonts->GetFontFamily (fontIndex, fontFamily.resetAndGetPointerAddress());
// Get the font faces
ComSmartPtr<IDWriteFont> dwFont;
uint32 fontFacesCount = 0;
fontFacesCount = fontFamily->GetFontCount();
for (uint32 i = 0; i < fontFacesCount; ++i)
{
hr = fontFamily->GetFont (i, dwFont.resetAndGetPointerAddress());
// Ignore any algorithmically generated bold and oblique styles..
if (dwFont->GetSimulations() == DWRITE_FONT_SIMULATIONS_NONE)
results.addIfNotAlreadyThere (getFontFaceName (dwFont));
}
}
else
#endif
{
results.add ("Regular");
results.add ("Italic");
results.add ("Bold");
results.add ("Bold Italic");
}
return results;
}
extern bool juce_isRunningInWine();
struct DefaultFontNames
{
DefaultFontNames()
{
if (juce_isRunningInWine())
{
// If we're running in Wine, then use fonts that might be available on Linux..
defaultSans = "Bitstream Vera Sans";
defaultSerif = "Bitstream Vera Serif";
defaultFixed = "Bitstream Vera Sans Mono";
}
else
{
defaultSans = "Verdana";
defaultSerif = "Times New Roman";
defaultFixed = "Lucida Console";
defaultFallback = "Tahoma"; // (contains plenty of unicode characters)
}
}
String defaultSans, defaultSerif, defaultFixed, defaultFallback;
};
Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
{
static DefaultFontNames defaultNames;
Font newFont (font);
const String& faceName = font.getTypefaceName();
if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans);
else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif);
else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed);
if (font.getTypefaceStyle() == getDefaultStyle())
newFont.setTypefaceStyle ("Regular");
return Typeface::createSystemTypefaceFor (newFont);
}
//==============================================================================
class WindowsTypeface : public Typeface
{
public:
WindowsTypeface (const Font& font)
: Typeface (font.getTypefaceName(), font.getTypefaceStyle()),
fontH (0), previousFontH (0),
dc (CreateCompatibleDC (0)), memoryFont (0),
ascent (1.0f), heightToPointsFactor (1.0f),
defaultGlyph (-1)
{
loadFont();
}
WindowsTypeface (const void* data, size_t dataSize)
: Typeface (String(), String()),
fontH (0), previousFontH (0),
dc (CreateCompatibleDC (0)), memoryFont (0),
ascent (1.0f), heightToPointsFactor (1.0f),
defaultGlyph (-1)
{
DWORD numInstalled = 0;
memoryFont = AddFontMemResourceEx (const_cast<void*> (data), (DWORD) dataSize,
nullptr, &numInstalled);
MemoryInputStream m (data, dataSize, false);
name = TTFNameExtractor::getTypefaceNameFromFile (m);
loadFont();
}
~WindowsTypeface()
{
SelectObject (dc, previousFontH); // Replacing the previous font before deleting the DC avoids a warning in BoundsChecker
DeleteDC (dc);
if (fontH != 0)
DeleteObject (fontH);
if (memoryFont != 0)
RemoveFontMemResourceEx (memoryFont);
}
float getAscent() const { return ascent; }
float getDescent() const { return 1.0f - ascent; }
float getHeightToPointsFactor() const { return heightToPointsFactor; }
float getStringWidth (const String& text)
{
const CharPointer_UTF16 utf16 (text.toUTF16());
const size_t numChars = utf16.length();
HeapBlock<uint16> results (numChars);
float x = 0;
if (GetGlyphIndices (dc, utf16, (int) numChars, reinterpret_cast<WORD*> (results.getData()),
GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR)
{
for (size_t i = 0; i < numChars; ++i)
x += getKerning (dc, results[i], (i + 1) < numChars ? results[i + 1] : -1);
}
return x;
}
void getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array <float>& xOffsets)
{
const CharPointer_UTF16 utf16 (text.toUTF16());
const size_t numChars = utf16.length();
HeapBlock<uint16> results (numChars);
float x = 0;
if (GetGlyphIndices (dc, utf16, (int) numChars, reinterpret_cast<WORD*> (results.getData()),
GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR)
{
resultGlyphs.ensureStorageAllocated ((int) numChars);
xOffsets.ensureStorageAllocated ((int) numChars + 1);
for (size_t i = 0; i < numChars; ++i)
{
resultGlyphs.add (results[i]);
xOffsets.add (x);
x += getKerning (dc, results[i], (i + 1) < numChars ? results[i + 1] : -1);
}
}
xOffsets.add (x);
}
bool getOutlineForGlyph (int glyphNumber, Path& glyphPath)
{
if (glyphNumber < 0)
glyphNumber = defaultGlyph;
GLYPHMETRICS gm;
// (although GetGlyphOutline returns a DWORD, it may be -1 on failure, so treat it as signed int..)
const int bufSize = (int) GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX,
&gm, 0, 0, &identityMatrix);
if (bufSize > 0)
{
HeapBlock<char> data (bufSize);
GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm,
bufSize, data, &identityMatrix);
const TTPOLYGONHEADER* pheader = reinterpret_cast<TTPOLYGONHEADER*> (data.getData());
const float scaleX = 1.0f / tm.tmHeight;
const float scaleY = -scaleX;
while ((char*) pheader < data + bufSize)
{
glyphPath.startNewSubPath (scaleX * pheader->pfxStart.x.value,
scaleY * pheader->pfxStart.y.value);
const TTPOLYCURVE* curve = (const TTPOLYCURVE*) ((const char*) pheader + sizeof (TTPOLYGONHEADER));
const char* const curveEnd = ((const char*) pheader) + pheader->cb;
while ((const char*) curve < curveEnd)
{
if (curve->wType == TT_PRIM_LINE)
{
for (int i = 0; i < curve->cpfx; ++i)
glyphPath.lineTo (scaleX * curve->apfx[i].x.value,
scaleY * curve->apfx[i].y.value);
}
else if (curve->wType == TT_PRIM_QSPLINE)
{
for (int i = 0; i < curve->cpfx - 1; ++i)
{
const float x2 = scaleX * curve->apfx[i].x.value;
const float y2 = scaleY * curve->apfx[i].y.value;
float x3 = scaleX * curve->apfx[i + 1].x.value;
float y3 = scaleY * curve->apfx[i + 1].y.value;
if (i < curve->cpfx - 2)
{
x3 = 0.5f * (x2 + x3);
y3 = 0.5f * (y2 + y3);
}
glyphPath.quadraticTo (x2, y2, x3, y3);
}
}
curve = (const TTPOLYCURVE*) &(curve->apfx [curve->cpfx]);
}
pheader = (const TTPOLYGONHEADER*) curve;
glyphPath.closeSubPath();
}
}
return true;
}
private:
static const MAT2 identityMatrix;
HFONT fontH;
HGDIOBJ previousFontH;
HDC dc;
TEXTMETRIC tm;
HANDLE memoryFont;
float ascent, heightToPointsFactor;
int defaultGlyph, heightInPoints;
struct KerningPair
{
int glyph1, glyph2;
float kerning;
bool operator== (const KerningPair& other) const noexcept
{
return glyph1 == other.glyph1 && glyph2 == other.glyph2;
}
bool operator< (const KerningPair& other) const noexcept
{
return glyph1 < other.glyph1
|| (glyph1 == other.glyph1 && glyph2 < other.glyph2);
}
};
SortedSet<KerningPair> kerningPairs;
void loadFont()
{
SetMapperFlags (dc, 0);
SetMapMode (dc, MM_TEXT);
LOGFONTW lf = { 0 };
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
lf.lfOutPrecision = OUT_OUTLINE_PRECIS;
lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
lf.lfQuality = PROOF_QUALITY;
lf.lfItalic = (BYTE) (style.contains ("Italic") ? TRUE : FALSE);
lf.lfWeight = style.contains ("Bold") ? FW_BOLD : FW_NORMAL;
lf.lfHeight = -256;
name.copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName));
HFONT standardSizedFont = CreateFontIndirect (&lf);
if (standardSizedFont != 0)
{
if ((previousFontH = SelectObject (dc, standardSizedFont)) != 0)
{
fontH = standardSizedFont;
OUTLINETEXTMETRIC otm;
if (GetOutlineTextMetrics (dc, sizeof (otm), &otm) != 0)
{
heightInPoints = otm.otmEMSquare;
lf.lfHeight = -(int) heightInPoints;
fontH = CreateFontIndirect (&lf);
SelectObject (dc, fontH);
DeleteObject (standardSizedFont);
}
}
}
if (GetTextMetrics (dc, &tm))
{
float dpi = (GetDeviceCaps (dc, LOGPIXELSX) + GetDeviceCaps (dc, LOGPIXELSY)) / 2.0f;
heightToPointsFactor = (dpi / GetDeviceCaps (dc, LOGPIXELSY)) * heightInPoints / (float) tm.tmHeight;
ascent = tm.tmAscent / (float) tm.tmHeight;
defaultGlyph = getGlyphForChar (dc, tm.tmDefaultChar);
createKerningPairs (dc, (float) tm.tmHeight);
}
}
void createKerningPairs (HDC hdc, const float height)
{
HeapBlock<KERNINGPAIR> rawKerning;
const DWORD numKPs = GetKerningPairs (hdc, 0, 0);
rawKerning.calloc (numKPs);
GetKerningPairs (hdc, numKPs, rawKerning);
kerningPairs.ensureStorageAllocated ((int) numKPs);
for (DWORD i = 0; i < numKPs; ++i)
{
KerningPair kp;
kp.glyph1 = getGlyphForChar (hdc, rawKerning[i].wFirst);
kp.glyph2 = getGlyphForChar (hdc, rawKerning[i].wSecond);
const int standardWidth = getGlyphWidth (hdc, kp.glyph1);
kp.kerning = (standardWidth + rawKerning[i].iKernAmount) / height;
kerningPairs.add (kp);
kp.glyph2 = -1; // add another entry for the standard width version..
kp.kerning = standardWidth / height;
kerningPairs.add (kp);
}
}
static int getGlyphForChar (HDC dc, juce_wchar character)
{
const WCHAR charToTest[] = { (WCHAR) character, 0 };
WORD index = 0;
if (GetGlyphIndices (dc, charToTest, 1, &index, GGI_MARK_NONEXISTING_GLYPHS) == GDI_ERROR
|| index == 0xffff)
return -1;
return index;
}
static int getGlyphWidth (HDC dc, int glyphNumber)
{
GLYPHMETRICS gm;
gm.gmCellIncX = 0;
GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, 0, &identityMatrix);
return gm.gmCellIncX;
}
float getKerning (HDC hdc, const int glyph1, const int glyph2)
{
KerningPair kp;
kp.glyph1 = glyph1;
kp.glyph2 = glyph2;
int index = kerningPairs.indexOf (kp);
if (index < 0)
{
kp.glyph2 = -1;
index = kerningPairs.indexOf (kp);
if (index < 0)
{
kp.glyph2 = -1;
kp.kerning = getGlyphWidth (hdc, kp.glyph1) / (float) tm.tmHeight;
kerningPairs.add (kp);
return kp.kerning;
}
}
return kerningPairs.getReference (index).kerning;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTypeface)
};
const MAT2 WindowsTypeface::identityMatrix = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 } };
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
{
#if JUCE_USE_DIRECTWRITE
SharedResourcePointer<Direct2DFactories> factories;
if (factories->systemFonts != nullptr)
{
std::unique_ptr<WindowsDirectWriteTypeface> wtf (new WindowsDirectWriteTypeface (font, factories->systemFonts));
if (wtf->loadedOk())
return wtf.release();
}
#endif
return new WindowsTypeface (font);
}
Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize)
{
return new WindowsTypeface (data, dataSize);
}
void Typeface::scanFolderForFonts (const File&)
{
jassertfalse; // not implemented on this platform
}
} // namespace juce

View File

@ -0,0 +1,30 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
Image JUCE_API getIconFromApplication (const String&, int) { return {}; }
}