mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-13 00:04:19 +00:00
Added Animated App template and examples
This commit is contained in:
parent
fefcf7aca6
commit
ff6520a89a
1141 changed files with 438491 additions and 94 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,334 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
Array<File> fonts;
|
||||
File ("/system/fonts").findChildFiles (fonts, File::findFiles, false, "*.ttf");
|
||||
|
||||
for (int i = 0; i < fonts.size(); ++i)
|
||||
results.addIfNotAlreadyThere (fonts.getReference(i).getFileNameWithoutExtension()
|
||||
.upToLastOccurrenceOf ("-", false, false));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
StringArray Font::findAllTypefaceStyles (const String& family)
|
||||
{
|
||||
StringArray results ("Regular");
|
||||
|
||||
Array<File> fonts;
|
||||
File ("/system/fonts").findChildFiles (fonts, File::findFiles, false, family + "-*.ttf");
|
||||
|
||||
for (int i = 0; i < fonts.size(); ++i)
|
||||
results.addIfNotAlreadyThere (fonts.getReference(i).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();
|
||||
|
||||
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)));
|
||||
|
||||
rect = GlobalRef (env->NewObject (RectClass, RectClass.constructor, 0, 0, 0, 0));
|
||||
|
||||
paint = GlobalRef (GraphicsHelpers::createPaint (Graphics::highResamplingQuality));
|
||||
const LocalRef<jobject> ignored (paint.callObjectMethod (Paint.setTypeface, typeface.get()));
|
||||
|
||||
paint.callVoidMethod (Paint.setTextSize, referenceFontSize);
|
||||
|
||||
const float fullAscent = std::abs (paint.callFloatMethod (Paint.ascent));
|
||||
const float fullDescent = paint.callFloatMethod (Paint.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 = text.length();
|
||||
jfloatArray widths = env->NewFloatArray (numChars);
|
||||
|
||||
const int numDone = paint.callIntMethod (Paint.getTextWidths, javaString (text).get(), widths);
|
||||
|
||||
HeapBlock<jfloat> localWidths (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 = text.length();
|
||||
jfloatArray widths = env->NewFloatArray (numChars);
|
||||
|
||||
const int numDone = paint.callIntMethod (Paint.getTextWidths, javaString (text).get(), widths);
|
||||
|
||||
HeapBlock<jfloat> localWidths (numDone);
|
||||
env->GetFloatArrayRegion (widths, 0, numDone, localWidths);
|
||||
env->DeleteLocalRef (widths);
|
||||
|
||||
String::CharPointerType s (text.getCharPointer());
|
||||
|
||||
xOffsets.add (0);
|
||||
|
||||
float x = 0;
|
||||
for (int i = 0; i < numDone; ++i)
|
||||
{
|
||||
glyphs.add ((int) s.getAndAdvance());
|
||||
x += localWidths[i];
|
||||
xOffsets.add (x * referenceFontToUnits);
|
||||
}
|
||||
}
|
||||
|
||||
bool getOutlineForGlyph (int /*glyphNumber*/, Path& /*destPath*/) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& t, float /*fontHeight*/) override
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
jobject matrix = GraphicsHelpers::createMatrix (env, AffineTransform::scale (referenceFontToUnits).followedBy (t));
|
||||
jintArray maskData = (jintArray) android.activity.callObjectMethod (JuceAppActivity.renderGlyph, (jchar) glyphNumber, paint.get(), matrix, rect.get());
|
||||
|
||||
env->DeleteLocalRef (matrix);
|
||||
|
||||
const int left = env->GetIntField (rect.get(), RectClass.left);
|
||||
const int top = env->GetIntField (rect.get(), RectClass.top);
|
||||
const int right = env->GetIntField (rect.get(), RectClass.right);
|
||||
const int bottom = env->GetIntField (rect.get(), RectClass.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*, size_t)
|
||||
{
|
||||
jassertfalse; // not yet implemented!
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Typeface::scanFolderForFonts (const File&)
|
||||
{
|
||||
jassertfalse; // not available unless using FreeType
|
||||
}
|
||||
|
||||
bool TextLayout::createNativeLayout (const AttributedString&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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 (Paint, Paint.constructor, constructorFlags);
|
||||
}
|
||||
|
||||
const jobject createMatrix (JNIEnv* env, const AffineTransform& t)
|
||||
{
|
||||
jobject m = env->NewObject (Matrix, Matrix.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, Matrix.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);
|
||||
}
|
||||
|
|
@ -0,0 +1,455 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
typedef ReferenceCountedObjectPtr <FTLibWrapper> Ptr;
|
||||
|
||||
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;
|
||||
|
||||
typedef ReferenceCountedObjectPtr<FTFaceWrapper> Ptr;
|
||||
|
||||
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_DeclareSingleton_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 = 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_ImplementSingleton_SingleThreaded (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& name, const String& style)
|
||||
{
|
||||
setCharacteristics (name, style,
|
||||
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, 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, 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;
|
||||
uint32 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 (character, rightCharCode, kerning.x / height);
|
||||
|
||||
rightCharCode = FT_Get_Next_Char (face, rightCharCode, &rightGlyphIndex);
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (FreeTypeTypeface)
|
||||
};
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
StringArray FTTypefaceList::getDefaultFontDirectories()
|
||||
{
|
||||
StringArray fontDirs;
|
||||
|
||||
fontDirs.addTokens (String (CharPointer_UTF8 (getenv ("JUCE_FONT_PATH"))), ";,", "");
|
||||
fontDirs.removeEmptyStrings (true);
|
||||
|
||||
if (fontDirs.size() == 0)
|
||||
{
|
||||
const ScopedPointer<XmlElement> fontsInfo (XmlDocument::parse (File ("/etc/fonts/fonts.conf")));
|
||||
|
||||
if (fontsInfo != nullptr)
|
||||
{
|
||||
forEachXmlChildElementWithTagName (*fontsInfo, e, "dir")
|
||||
{
|
||||
String fontPath (e->getAllSubText().trim());
|
||||
|
||||
if (fontPath.isNotEmpty())
|
||||
{
|
||||
if (e->getStringAttribute ("prefix") == "xdg")
|
||||
{
|
||||
String xdgDataHome (SystemStats::getEnvironmentVariable ("XDG_DATA_HOME", String()));
|
||||
|
||||
if (xdgDataHome.trimStart().isEmpty())
|
||||
xdgDataHome = "~/.local/share";
|
||||
|
||||
fontPath = File (xdgDataHome).getChildFile (fontPath).getFullPathName();
|
||||
}
|
||||
|
||||
fontDirs.add (fontPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fontDirs.size() == 0)
|
||||
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 (int j = 0; j < choices.size(); ++j)
|
||||
if (names.contains (choices[j], true))
|
||||
return choices[j];
|
||||
|
||||
for (int j = 0; j < choices.size(); ++j)
|
||||
for (int i = 0; i < names.size(); ++i)
|
||||
if (names[i].startsWithIgnoreCase (choices[j]))
|
||||
return names[i];
|
||||
|
||||
for (int j = 0; j < choices.size(); ++j)
|
||||
for (int i = 0; i < names.size(); ++i)
|
||||
if (names[i].containsIgnoreCase (choices[j]))
|
||||
return names[i];
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_MAC_COREGRAPHICSCONTEXT_H_INCLUDED
|
||||
#define JUCE_MAC_COREGRAPHICSCONTEXT_H_INCLUDED
|
||||
|
||||
//==============================================================================
|
||||
class CoreGraphicsContext : public LowLevelGraphicsContext
|
||||
{
|
||||
public:
|
||||
CoreGraphicsContext (CGContextRef context, const float flipHeight, const 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;
|
||||
CGFunctionCallbacks gradientCallbacks;
|
||||
mutable Rectangle<int> lastClipRect;
|
||||
mutable bool lastClipRectIsValid;
|
||||
|
||||
struct SavedState
|
||||
{
|
||||
SavedState();
|
||||
SavedState (const SavedState&);
|
||||
~SavedState();
|
||||
|
||||
void setFill (const FillType& newFill);
|
||||
CGShadingRef getShading (CoreGraphicsContext& owner);
|
||||
|
||||
static void gradientCallback (void* info, const CGFloat* inData, CGFloat* outData);
|
||||
|
||||
FillType fillType;
|
||||
Font font;
|
||||
CGFontRef fontRef;
|
||||
CGAffineTransform fontTransform;
|
||||
|
||||
private:
|
||||
CGShadingRef shading;
|
||||
HeapBlock <PixelARGB> gradientLookupTable;
|
||||
int numGradientLookupEntries;
|
||||
};
|
||||
|
||||
ScopedPointer <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)
|
||||
};
|
||||
|
||||
#endif // JUCE_MAC_COREGRAPHICSCONTEXT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,927 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "juce_mac_CoreGraphicsContext.h"
|
||||
|
||||
//==============================================================================
|
||||
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 * 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* 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) (srcData.lineStride * srcData.height));
|
||||
provider = CGDataProviderCreateWithCFData (data);
|
||||
CFRelease (data);
|
||||
}
|
||||
else
|
||||
{
|
||||
provider = CGDataProviderCreateWithData (0, srcData.data, (size_t) (srcData.lineStride * 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);
|
||||
CGContextSetShouldAntialias (context, true);
|
||||
CGContextSetBlendMode (context, kCGBlendModeNormal);
|
||||
rgbColourSpace = CGColorSpaceCreateDeviceRGB();
|
||||
greyColourSpace = CGColorSpaceCreateDeviceGray();
|
||||
gradientCallbacks.version = 0;
|
||||
gradientCallbacks.evaluate = SavedState::gradientCallback;
|
||||
gradientCallbacks.releaseInfo = 0;
|
||||
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;
|
||||
|
||||
// return targetScale * (float) (t.a + 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;
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t numRects = (size_t) clipRegion.getNumRectangles();
|
||||
HeapBlock <CGRect> rects (numRects);
|
||||
|
||||
int i = 0;
|
||||
for (const Rectangle<int>* r = clipRegion.begin(), * const e = clipRegion.end(); r != e; ++r)
|
||||
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);
|
||||
CGContextClip (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 (SavedState* const top = stateStack.getLast())
|
||||
{
|
||||
state = 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)
|
||||
{
|
||||
CGContextSetInterpolationQuality (context, quality == Graphics::lowResamplingQuality
|
||||
? kCGInterpolationLow
|
||||
: kCGInterpolationHigh);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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)
|
||||
{
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
|
||||
CGContextClearRect (context, cgRect);
|
||||
#else
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
|
||||
if (CGContextDrawLinearGradient == 0) // (just a way of checking whether we're running in 10.5 or later)
|
||||
CGContextClearRect (context, cgRect);
|
||||
else
|
||||
#endif
|
||||
CGContextSetBlendMode (context, kCGBlendModeCopy);
|
||||
#endif
|
||||
|
||||
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, 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
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
||||
// 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
|
||||
#endif
|
||||
{
|
||||
// Fallback to manually doing a tiled fill on 10.4
|
||||
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::identity);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::fillRectList (const RectangleList<float>& list)
|
||||
{
|
||||
HeapBlock<CGRect> rects ((size_t) list.getNumRectangles());
|
||||
|
||||
size_t num = 0;
|
||||
for (const Rectangle<float>* r = list.begin(), * const e = list.end(); r != e; ++r)
|
||||
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)
|
||||
{
|
||||
#if JUCE_CORETEXT_AVAILABLE
|
||||
CoreTextTypeLayout::drawToCGContext (text, area, context, (float) flipHeight);
|
||||
return true;
|
||||
#else
|
||||
(void) text; (void) area;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
CoreGraphicsContext::SavedState::SavedState()
|
||||
: font (1.0f), fontRef (0), fontTransform (CGAffineTransformIdentity),
|
||||
shading (0), numGradientLookupEntries (0)
|
||||
{
|
||||
}
|
||||
|
||||
CoreGraphicsContext::SavedState::SavedState (const SavedState& other)
|
||||
: fillType (other.fillType), font (other.font), fontRef (other.fontRef),
|
||||
fontTransform (other.fontTransform), shading (0),
|
||||
gradientLookupTable ((size_t) other.numGradientLookupEntries),
|
||||
numGradientLookupEntries (other.numGradientLookupEntries)
|
||||
{
|
||||
memcpy (gradientLookupTable, other.gradientLookupTable, sizeof (PixelARGB) * (size_t) numGradientLookupEntries);
|
||||
}
|
||||
|
||||
CoreGraphicsContext::SavedState::~SavedState()
|
||||
{
|
||||
if (shading != 0)
|
||||
CGShadingRelease (shading);
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::SavedState::setFill (const FillType& newFill)
|
||||
{
|
||||
fillType = newFill;
|
||||
|
||||
if (fillType.isGradient() && shading != 0)
|
||||
{
|
||||
CGShadingRelease (shading);
|
||||
shading = 0;
|
||||
}
|
||||
}
|
||||
|
||||
CGShadingRef CoreGraphicsContext::SavedState::getShading (CoreGraphicsContext& owner)
|
||||
{
|
||||
if (shading == 0)
|
||||
{
|
||||
ColourGradient& g = *(fillType.gradient);
|
||||
numGradientLookupEntries = g.createLookupTable (fillType.transform, gradientLookupTable) - 1;
|
||||
|
||||
CGFunctionRef function = CGFunctionCreate (this, 1, 0, 4, 0, &(owner.gradientCallbacks));
|
||||
CGPoint p1 (convertToCGPoint (g.point1));
|
||||
|
||||
if (g.isRadial)
|
||||
{
|
||||
shading = CGShadingCreateRadial (owner.rgbColourSpace, p1, 0,
|
||||
p1, g.point1.getDistanceFrom (g.point2),
|
||||
function, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
shading = CGShadingCreateAxial (owner.rgbColourSpace, p1,
|
||||
convertToCGPoint (g.point2),
|
||||
function, true, true);
|
||||
}
|
||||
|
||||
CGFunctionRelease (function);
|
||||
}
|
||||
|
||||
return shading;
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::SavedState::gradientCallback (void* info, const CGFloat* inData, CGFloat* outData)
|
||||
{
|
||||
const SavedState* const s = static_cast <const SavedState*> (info);
|
||||
|
||||
const int index = roundToInt (s->numGradientLookupEntries * inData[0]);
|
||||
PixelARGB colour (s->gradientLookupTable [jlimit (0, s->numGradientLookupEntries, index)]);
|
||||
colour.unpremultiply();
|
||||
|
||||
outData[0] = colour.getRed() / 255.0f;
|
||||
outData[1] = colour.getGreen() / 255.0f;
|
||||
outData[2] = colour.getBlue() / 255.0f;
|
||||
outData[3] = colour.getAlpha() / 255.0f;
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::drawGradient()
|
||||
{
|
||||
flip();
|
||||
applyTransform (state->fillType.transform);
|
||||
|
||||
CGContextSetInterpolationQuality (context, kCGInterpolationDefault); // (This is required for 10.4, where there's a crash if
|
||||
// you draw a gradient with high quality interp enabled).
|
||||
CGContextSetAlpha (context, state->fillType.getOpacity());
|
||||
CGContextDrawShading (context, state->getShading (*this));
|
||||
}
|
||||
|
||||
void CoreGraphicsContext::createPath (const Path& path) const
|
||||
{
|
||||
CGContextBeginPath (context);
|
||||
Path::Iterator i (path);
|
||||
|
||||
while (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);
|
||||
Path::Iterator i (path);
|
||||
|
||||
while (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::null;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JUCE_MAC
|
||||
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;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_MAC_COREGRAPHICSHELPERS_H_INCLUDED
|
||||
#define JUCE_MAC_COREGRAPHICSHELPERS_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
namespace
|
||||
{
|
||||
template <class RectType>
|
||||
Rectangle<int> convertToRectInt (const 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 (const RectType& r) noexcept
|
||||
{
|
||||
return Rectangle<float> (r.origin.x, r.origin.y, r.size.width, r.size.height);
|
||||
}
|
||||
|
||||
template <class RectType>
|
||||
CGRect convertToCGRect (const RectType& r) noexcept
|
||||
{
|
||||
return CGRectMake ((CGFloat) r.getX(), (CGFloat) r.getY(), (CGFloat) r.getWidth(), (CGFloat) r.getHeight());
|
||||
}
|
||||
|
||||
template <typename PointType>
|
||||
CGPoint convertToCGPoint (const PointType& p) noexcept
|
||||
{
|
||||
return CGPointMake ((CGFloat) p.x, (CGFloat) p.y);
|
||||
}
|
||||
}
|
||||
|
||||
extern CGImageRef juce_createCoreGraphicsImage (const Image&, CGColorSpaceRef, bool mustOutliveSource);
|
||||
extern CGContextRef juce_getImageContext (const Image&);
|
||||
|
||||
#endif // JUCE_MAC_COREGRAPHICSHELPERS_H_INCLUDED
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,838 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
class Direct2DLowLevelGraphicsContext : public LowLevelGraphicsContext
|
||||
{
|
||||
public:
|
||||
Direct2DLowLevelGraphicsContext (HWND hwnd_)
|
||||
: hwnd (hwnd_),
|
||||
currentState (nullptr)
|
||||
{
|
||||
RECT windowRect;
|
||||
GetClientRect (hwnd, &windowRect);
|
||||
D2D1_SIZE_U size = { windowRect.right - windowRect.left, 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 (factories->d2dFactory != nullptr)
|
||||
{
|
||||
HRESULT hr = factories->d2dFactory->CreateHwndRenderTarget (props, propsHwnd, renderingTarget.resetAndGetPointerAddress());
|
||||
jassert (SUCCEEDED (hr)); (void) hr;
|
||||
hr = renderingTarget->CreateSolidColorBrush (D2D1::ColorF::ColorF (0.0f, 0.0f, 0.0f, 1.0f), colourBrush.resetAndGetPointerAddress());
|
||||
}
|
||||
}
|
||||
|
||||
~Direct2DLowLevelGraphicsContext()
|
||||
{
|
||||
states.clear();
|
||||
}
|
||||
|
||||
void resized()
|
||||
{
|
||||
RECT windowRect;
|
||||
GetClientRect (hwnd, &windowRect);
|
||||
D2D1_SIZE_U size = { windowRect.right - windowRect.left, windowRect.bottom - windowRect.top };
|
||||
|
||||
renderingTarget->Resize (size);
|
||||
bounds.setSize (size.width, size.height);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
renderingTarget->Clear (D2D1::ColorF (D2D1::ColorF::White, 0.0f)); // xxx why white and not black?
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
renderingTarget->BeginDraw();
|
||||
saveState();
|
||||
}
|
||||
|
||||
void end()
|
||||
{
|
||||
states.clear();
|
||||
currentState = 0;
|
||||
renderingTarget->EndDraw();
|
||||
renderingTarget->CheckWindowState();
|
||||
}
|
||||
|
||||
bool isVectorDevice() const { return false; }
|
||||
|
||||
void setOrigin (Point<int> o)
|
||||
{
|
||||
addTransform (AffineTransform::translation ((float) o.x, (float) o.y));
|
||||
}
|
||||
|
||||
void addTransform (const AffineTransform& transform)
|
||||
{
|
||||
currentState->transform = transform.followedBy (currentState->transform);
|
||||
}
|
||||
|
||||
float getPhysicalPixelScaleFactor()
|
||||
{
|
||||
return currentState->transform.getScaleFactor();
|
||||
}
|
||||
|
||||
bool clipToRectangle (const Rectangle<int>& r)
|
||||
{
|
||||
currentState->clipToRectangle (r);
|
||||
return ! isClipEmpty();
|
||||
}
|
||||
|
||||
bool clipToRectangleList (const RectangleList<int>& clipRegion)
|
||||
{
|
||||
currentState->clipToRectList (rectListToPathGeometry (clipRegion));
|
||||
return ! isClipEmpty();
|
||||
}
|
||||
|
||||
void excludeClipRectangle (const Rectangle<int>&)
|
||||
{
|
||||
//xxx
|
||||
}
|
||||
|
||||
void clipToPath (const Path& path, const AffineTransform& transform)
|
||||
{
|
||||
currentState->clipToPath (pathToPathGeometry (path, transform));
|
||||
}
|
||||
|
||||
void clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform)
|
||||
{
|
||||
currentState->clipToImage (sourceImage, transform);
|
||||
}
|
||||
|
||||
bool clipRegionIntersects (const Rectangle<int>& r)
|
||||
{
|
||||
return currentState->clipRect.intersects (r.toFloat().transformed (currentState->transform).getSmallestIntegerContainer());
|
||||
}
|
||||
|
||||
Rectangle<int> getClipBounds() const
|
||||
{
|
||||
// xxx could this take into account complex clip regions?
|
||||
return currentState->clipRect.toFloat().transformed (currentState->transform.inverted()).getSmallestIntegerContainer();
|
||||
}
|
||||
|
||||
bool isClipEmpty() const
|
||||
{
|
||||
return currentState->clipRect.isEmpty();
|
||||
}
|
||||
|
||||
void saveState()
|
||||
{
|
||||
states.add (new SavedState (*this));
|
||||
currentState = states.getLast();
|
||||
}
|
||||
|
||||
void restoreState()
|
||||
{
|
||||
jassert (states.size() > 1) //you should never pop the last state!
|
||||
states.removeLast (1);
|
||||
currentState = states.getLast();
|
||||
}
|
||||
|
||||
void beginTransparencyLayer (float /*opacity*/)
|
||||
{
|
||||
jassertfalse; //xxx todo
|
||||
}
|
||||
|
||||
void endTransparencyLayer()
|
||||
{
|
||||
jassertfalse; //xxx todo
|
||||
}
|
||||
|
||||
void setFill (const FillType& fillType)
|
||||
{
|
||||
currentState->setFill (fillType);
|
||||
}
|
||||
|
||||
void setOpacity (float newOpacity)
|
||||
{
|
||||
currentState->setOpacity (newOpacity);
|
||||
}
|
||||
|
||||
void setInterpolationQuality (Graphics::ResamplingQuality /*quality*/)
|
||||
{
|
||||
}
|
||||
|
||||
void fillRect (const Rectangle<int>& r, bool /*replaceExistingContents*/)
|
||||
{
|
||||
fillRect (r.toFloat());
|
||||
}
|
||||
|
||||
void fillRect (const Rectangle<float>& r)
|
||||
{
|
||||
renderingTarget->SetTransform (transformToMatrix (currentState->transform));
|
||||
currentState->createBrush();
|
||||
renderingTarget->FillRectangle (rectangleToRectF (r), currentState->currentBrush);
|
||||
renderingTarget->SetTransform (D2D1::IdentityMatrix());
|
||||
}
|
||||
|
||||
void fillRectList (const RectangleList<float>& list)
|
||||
{
|
||||
for (const Rectangle<float>* r = list.begin(), * const e = list.end(); r != e; ++r)
|
||||
fillRect (*r);
|
||||
}
|
||||
|
||||
void fillPath (const Path& p, const AffineTransform& transform)
|
||||
{
|
||||
currentState->createBrush();
|
||||
ComSmartPtr <ID2D1Geometry> geometry (pathToPathGeometry (p, transform.followedBy (currentState->transform)));
|
||||
|
||||
if (renderingTarget != nullptr)
|
||||
renderingTarget->FillGeometry (geometry, currentState->currentBrush);
|
||||
}
|
||||
|
||||
void drawImage (const Image& image, const AffineTransform& transform)
|
||||
{
|
||||
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 = renderingTarget->GetPixelFormat();
|
||||
bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
|
||||
|
||||
{
|
||||
ComSmartPtr <ID2D1Bitmap> tempBitmap;
|
||||
renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, tempBitmap.resetAndGetPointerAddress());
|
||||
if (tempBitmap != nullptr)
|
||||
renderingTarget->DrawBitmap (tempBitmap);
|
||||
}
|
||||
|
||||
renderingTarget->SetTransform (D2D1::IdentityMatrix());
|
||||
}
|
||||
|
||||
void 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
|
||||
renderingTarget->SetTransform (transformToMatrix (currentState->transform));
|
||||
currentState->createBrush();
|
||||
|
||||
renderingTarget->DrawLine (D2D1::Point2F (line.getStartX(), line.getStartY()),
|
||||
D2D1::Point2F (line.getEndX(), line.getEndY()),
|
||||
currentState->currentBrush);
|
||||
renderingTarget->SetTransform (D2D1::IdentityMatrix());
|
||||
}
|
||||
|
||||
void setFont (const Font& newFont)
|
||||
{
|
||||
currentState->setFont (newFont);
|
||||
}
|
||||
|
||||
const Font& getFont()
|
||||
{
|
||||
return currentState->font;
|
||||
}
|
||||
|
||||
void drawGlyph (int glyphNumber, const AffineTransform& transform)
|
||||
{
|
||||
currentState->createBrush();
|
||||
currentState->createFont();
|
||||
|
||||
float hScale = currentState->font.getHorizontalScale();
|
||||
|
||||
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;
|
||||
|
||||
renderingTarget->DrawGlyphRun (D2D1::Point2F (0, 0), &glyphRun, currentState->currentBrush);
|
||||
renderingTarget->SetTransform (D2D1::IdentityMatrix());
|
||||
}
|
||||
|
||||
bool drawTextLayout (const AttributedString& text, const Rectangle<float>& area)
|
||||
{
|
||||
renderingTarget->SetTransform (transformToMatrix (currentState->transform));
|
||||
|
||||
DirectWriteTypeLayout::drawToD2DContext (text, area, renderingTarget, factories->directWriteFactory,
|
||||
factories->d2dFactory, factories->systemFonts);
|
||||
|
||||
renderingTarget->SetTransform (D2D1::IdentityMatrix());
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class SavedState
|
||||
{
|
||||
public:
|
||||
SavedState (Direct2DLowLevelGraphicsContext& owner_)
|
||||
: owner (owner_), currentBrush (0),
|
||||
fontHeightToEmSizeFactor (1.0f), currentFontFace (0),
|
||||
clipsRect (false), shouldClipRect (false),
|
||||
clipsRectList (false), shouldClipRectList (false),
|
||||
clipsComplex (false), shouldClipComplex (false),
|
||||
clipsBitmap (false), shouldClipBitmap (false)
|
||||
{
|
||||
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.renderingTarget->GetPixelSize());
|
||||
clipRect.setSize (size.width, size.height);
|
||||
setFill (FillType (Colours::black));
|
||||
}
|
||||
}
|
||||
|
||||
~SavedState()
|
||||
{
|
||||
clearClip();
|
||||
clearFont();
|
||||
clearFill();
|
||||
clearPathClip();
|
||||
clearImageClip();
|
||||
complexClipLayer = 0;
|
||||
bitmapMaskLayer = 0;
|
||||
}
|
||||
|
||||
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 = 0;
|
||||
shouldClipComplex = false;
|
||||
}
|
||||
}
|
||||
|
||||
void clipToPath (ID2D1Geometry* geometry)
|
||||
{
|
||||
clearPathClip();
|
||||
|
||||
if (complexClipLayer == 0)
|
||||
owner.renderingTarget->CreateLayer (complexClipLayer.resetAndGetPointerAddress());
|
||||
|
||||
complexClipGeometry = geometry;
|
||||
shouldClipComplex = true;
|
||||
pushClips();
|
||||
}
|
||||
|
||||
void clearRectListClip()
|
||||
{
|
||||
popClips();
|
||||
|
||||
if (shouldClipRectList)
|
||||
{
|
||||
rectListGeometry = 0;
|
||||
shouldClipRectList = false;
|
||||
}
|
||||
}
|
||||
|
||||
void clipToRectList (ID2D1Geometry* geometry)
|
||||
{
|
||||
clearRectListClip();
|
||||
|
||||
if (rectListLayer == 0)
|
||||
owner.renderingTarget->CreateLayer (rectListLayer.resetAndGetPointerAddress());
|
||||
|
||||
rectListGeometry = geometry;
|
||||
shouldClipRectList = true;
|
||||
pushClips();
|
||||
}
|
||||
|
||||
void clearImageClip()
|
||||
{
|
||||
popClips();
|
||||
|
||||
if (shouldClipBitmap)
|
||||
{
|
||||
maskBitmap = 0;
|
||||
bitmapMaskBrush = 0;
|
||||
shouldClipBitmap = false;
|
||||
}
|
||||
}
|
||||
|
||||
void clipToImage (const Image& image, const AffineTransform& transform)
|
||||
{
|
||||
clearImageClip();
|
||||
|
||||
if (bitmapMaskLayer == 0)
|
||||
owner.renderingTarget->CreateLayer (bitmapMaskLayer.resetAndGetPointerAddress());
|
||||
|
||||
D2D1_BRUSH_PROPERTIES brushProps;
|
||||
brushProps.opacity = 1;
|
||||
brushProps.transform = transformToMatrix (transform);
|
||||
|
||||
D2D1_BITMAP_BRUSH_PROPERTIES bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP);
|
||||
|
||||
D2D1_SIZE_U size;
|
||||
size.width = image.getWidth();
|
||||
size.height = image.getHeight();
|
||||
|
||||
D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties();
|
||||
|
||||
maskImage = image.convertedToFormat (Image::ARGB);
|
||||
Image::BitmapData bd (this->image, Image::BitmapData::readOnly); // xxx should be maskImage?
|
||||
bp.pixelFormat = owner.renderingTarget->GetPixelFormat();
|
||||
bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
|
||||
|
||||
HRESULT hr = owner.renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, maskBitmap.resetAndGetPointerAddress());
|
||||
hr = owner.renderingTarget->CreateBitmapBrush (maskBitmap, bmProps, brushProps, bitmapMaskBrush.resetAndGetPointerAddress());
|
||||
|
||||
imageMaskLayerParams = D2D1::LayerParameters();
|
||||
imageMaskLayerParams.opacityBrush = bitmapMaskBrush;
|
||||
|
||||
shouldClipBitmap = true;
|
||||
pushClips();
|
||||
}
|
||||
|
||||
void popClips()
|
||||
{
|
||||
if (clipsBitmap)
|
||||
{
|
||||
owner.renderingTarget->PopLayer();
|
||||
clipsBitmap = false;
|
||||
}
|
||||
|
||||
if (clipsComplex)
|
||||
{
|
||||
owner.renderingTarget->PopLayer();
|
||||
clipsComplex = false;
|
||||
}
|
||||
|
||||
if (clipsRectList)
|
||||
{
|
||||
owner.renderingTarget->PopLayer();
|
||||
clipsRectList = false;
|
||||
}
|
||||
|
||||
if (clipsRect)
|
||||
{
|
||||
owner.renderingTarget->PopAxisAlignedClip();
|
||||
clipsRect = false;
|
||||
}
|
||||
}
|
||||
|
||||
void pushClips()
|
||||
{
|
||||
if (shouldClipRect && ! clipsRect)
|
||||
{
|
||||
owner.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.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.renderingTarget->PushLayer (layerParams, complexClipLayer);
|
||||
clipsComplex = true;
|
||||
}
|
||||
|
||||
if (shouldClipBitmap && ! clipsBitmap)
|
||||
{
|
||||
owner.renderingTarget->PushLayer (imageMaskLayerParams, bitmapMaskLayer);
|
||||
clipsBitmap = true;
|
||||
}
|
||||
}
|
||||
|
||||
void setFill (const FillType& newFillType)
|
||||
{
|
||||
if (fillType != newFillType)
|
||||
{
|
||||
fillType = newFillType;
|
||||
clearFill();
|
||||
}
|
||||
}
|
||||
|
||||
void clearFont()
|
||||
{
|
||||
currentFontFace = localFontFace = 0;
|
||||
}
|
||||
|
||||
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->unitsToHeightScaleFactor();
|
||||
}
|
||||
}
|
||||
|
||||
void setOpacity (float newOpacity)
|
||||
{
|
||||
fillType.setOpacity (newOpacity);
|
||||
|
||||
if (currentBrush != nullptr)
|
||||
currentBrush->SetOpacity (newOpacity);
|
||||
}
|
||||
|
||||
void clearFill()
|
||||
{
|
||||
gradientStops = 0;
|
||||
linearGradient = 0;
|
||||
radialGradient = 0;
|
||||
bitmap = 0;
|
||||
bitmapBrush = 0;
|
||||
currentBrush = 0;
|
||||
}
|
||||
|
||||
void createBrush()
|
||||
{
|
||||
if (currentBrush == 0)
|
||||
{
|
||||
if (fillType.isColour())
|
||||
{
|
||||
D2D1_COLOR_F colour = colourToD2D (fillType.colour);
|
||||
owner.colourBrush->SetColor (colour);
|
||||
currentBrush = owner.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.renderingTarget->GetPixelFormat();
|
||||
bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
|
||||
|
||||
HRESULT hr = owner.renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, bitmap.resetAndGetPointerAddress());
|
||||
hr = owner.renderingTarget->CreateBitmapBrush (bitmap, bmProps, brushProps, bitmapBrush.resetAndGetPointerAddress());
|
||||
|
||||
currentBrush = bitmapBrush;
|
||||
}
|
||||
else if (fillType.isGradient())
|
||||
{
|
||||
gradientStops = 0;
|
||||
|
||||
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.renderingTarget->CreateGradientStopCollection (stops.getData(), numColors, gradientStops.resetAndGetPointerAddress());
|
||||
|
||||
if (fillType.gradient->isRadial)
|
||||
{
|
||||
radialGradient = 0;
|
||||
|
||||
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.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.renderingTarget->CreateLinearGradientBrush (props, brushProps, gradientStops, linearGradient.resetAndGetPointerAddress());
|
||||
|
||||
currentBrush = linearGradient;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//xxx most of these members should probably be private...
|
||||
|
||||
Direct2DLowLevelGraphicsContext& owner;
|
||||
|
||||
AffineTransform transform;
|
||||
|
||||
Font font;
|
||||
float fontHeightToEmSizeFactor;
|
||||
IDWriteFontFace* currentFontFace;
|
||||
ComSmartPtr <IDWriteFontFace> localFontFace;
|
||||
|
||||
FillType fillType;
|
||||
|
||||
Image image;
|
||||
ComSmartPtr <ID2D1Bitmap> bitmap; // xxx needs a better name - what is this for??
|
||||
|
||||
Rectangle<int> clipRect;
|
||||
bool clipsRect, shouldClipRect;
|
||||
|
||||
ComSmartPtr <ID2D1Geometry> complexClipGeometry;
|
||||
D2D1_LAYER_PARAMETERS complexClipLayerParams;
|
||||
ComSmartPtr <ID2D1Layer> complexClipLayer;
|
||||
bool clipsComplex, shouldClipComplex;
|
||||
|
||||
ComSmartPtr <ID2D1Geometry> rectListGeometry;
|
||||
D2D1_LAYER_PARAMETERS rectListLayerParams;
|
||||
ComSmartPtr <ID2D1Layer> rectListLayer;
|
||||
bool clipsRectList, shouldClipRectList;
|
||||
|
||||
Image maskImage;
|
||||
D2D1_LAYER_PARAMETERS imageMaskLayerParams;
|
||||
ComSmartPtr <ID2D1Layer> bitmapMaskLayer;
|
||||
ComSmartPtr <ID2D1Bitmap> maskBitmap;
|
||||
ComSmartPtr <ID2D1BitmapBrush> bitmapMaskBrush;
|
||||
bool clipsBitmap, shouldClipBitmap;
|
||||
|
||||
ID2D1Brush* currentBrush;
|
||||
ComSmartPtr <ID2D1BitmapBrush> bitmapBrush;
|
||||
ComSmartPtr <ID2D1LinearGradientBrush> linearGradient;
|
||||
ComSmartPtr <ID2D1RadialGradientBrush> radialGradient;
|
||||
ComSmartPtr <ID2D1GradientStopCollection> gradientStops;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedState)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
private:
|
||||
SharedResourcePointer<Direct2DFactories> factories;
|
||||
HWND hwnd;
|
||||
ComSmartPtr <ID2D1HwndRenderTarget> renderingTarget;
|
||||
ComSmartPtr <ID2D1SolidColorBrush> colourBrush;
|
||||
Rectangle<int> bounds;
|
||||
|
||||
SavedState* currentState;
|
||||
OwnedArray<SavedState> states;
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
static 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 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)
|
||||
{
|
||||
sink->BeginFigure (pointTransformed (rect.getX(), rect.getY()), D2D1_FIGURE_BEGIN_FILLED);
|
||||
sink->AddLine (pointTransformed (rect.getRight(), rect.getY()));
|
||||
sink->AddLine (pointTransformed (rect.getRight(), rect.getBottom()));
|
||||
sink->AddLine (pointTransformed (rect.getX(), rect.getBottom()));
|
||||
sink->EndFigure (D2D1_FIGURE_END_CLOSED);
|
||||
}
|
||||
|
||||
static 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);
|
||||
|
||||
hr = sink->Close();
|
||||
return p;
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DLowLevelGraphicsContext)
|
||||
};
|
||||
|
|
@ -0,0 +1,436 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
//==================================================================================================
|
||||
#if JUCE_USE_DIRECTWRITE
|
||||
namespace DirectWriteTypeLayout
|
||||
{
|
||||
class CustomDirectWriteTextRenderer : public ComBaseClassHelper<IDWriteTextRenderer>
|
||||
{
|
||||
public:
|
||||
CustomDirectWriteTextRenderer (IDWriteFontCollection* const fonts, const AttributedString& as)
|
||||
: ComBaseClassHelper<IDWriteTextRenderer> (0),
|
||||
attributedString (as),
|
||||
fontCollection (fonts),
|
||||
currentLine (-1),
|
||||
lastOriginY (-10000.0f)
|
||||
{
|
||||
}
|
||||
|
||||
JUCE_COMRESULT QueryInterface (REFIID refId, void** result)
|
||||
{
|
||||
if (refId == __uuidof (IDWritePixelSnapping))
|
||||
return castToType <IDWritePixelSnapping> (result);
|
||||
|
||||
return ComBaseClassHelper<IDWriteTextRenderer>::QueryInterface (refId, result);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT IsPixelSnappingDisabled (void* /*clientDrawingContext*/, BOOL* isDisabled)
|
||||
{
|
||||
*isDisabled = FALSE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetCurrentTransform (void*, DWRITE_MATRIX*) { return S_OK; }
|
||||
JUCE_COMRESULT GetPixelsPerDip (void*, FLOAT*) { return S_OK; }
|
||||
JUCE_COMRESULT DrawUnderline (void*, FLOAT, FLOAT, DWRITE_UNDERLINE const*, IUnknown*) { return S_OK; }
|
||||
JUCE_COMRESULT DrawStrikethrough (void*, FLOAT, FLOAT, DWRITE_STRIKETHROUGH const*, IUnknown*) { return S_OK; }
|
||||
JUCE_COMRESULT DrawInlineObject (void*, FLOAT, FLOAT, IDWriteInlineObject*, BOOL, BOOL, IUnknown*) { 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)
|
||||
{
|
||||
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 newLine = new TextLayout::Line();
|
||||
layout->addLine (newLine);
|
||||
|
||||
newLine->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;
|
||||
|
||||
for (UINT32 i = 0; i < glyphRun->glyphCount; ++i)
|
||||
{
|
||||
const float advance = glyphRun->glyphAdvances[i];
|
||||
|
||||
if ((glyphRun->bidiLevel & 1) != 0)
|
||||
x -= advance; // RTL text
|
||||
|
||||
glyphRunLayout->glyphs.add (TextLayout::Glyph (glyphRun->glyphIndices[i],
|
||||
Point<float> (x, baselineOriginY - lineOrigin.y),
|
||||
advance));
|
||||
|
||||
if ((glyphRun->bidiLevel & 1) == 0)
|
||||
x += advance; // LTR text
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
const AttributedString& attributedString;
|
||||
IDWriteFontCollection* const 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)
|
||||
{
|
||||
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 (DWRITE_GLYPH_RUN const* glyphRun, float fontHeight)
|
||||
{
|
||||
for (int i = 0; i < attributedString.getNumAttributes(); ++i)
|
||||
if (const Font* font = attributedString.getAttribute(i)->getFont())
|
||||
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* const 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* const 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* const renderTarget, IDWriteFontCollection* const fontCollection)
|
||||
{
|
||||
DWRITE_TEXT_RANGE range;
|
||||
range.startPosition = attr.range.getStart();
|
||||
range.length = jmin (attr.range.getLength(), textLen - attr.range.getStart());
|
||||
|
||||
if (const Font* const font = attr.getFont())
|
||||
{
|
||||
const String familyName (FontStyleHelpers::getConcreteFamilyName (*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 (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 (font->getHeight() * fontHeightToEmSizeFactor, range);
|
||||
}
|
||||
|
||||
if (const Colour* const colour = attr.getColour())
|
||||
{
|
||||
ComSmartPtr<ID2D1SolidColorBrush> d2dBrush;
|
||||
renderTarget->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF (colour->getFloatRed(),
|
||||
colour->getFloatGreen(),
|
||||
colour->getFloatBlue(),
|
||||
colour->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* const renderTarget, IDWriteFactory* const directWriteFactory,
|
||||
IDWriteFontCollection* const 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());
|
||||
|
||||
const float defaultFontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (dwFont);
|
||||
|
||||
jassert (directWriteFactory != nullptr);
|
||||
|
||||
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);
|
||||
|
||||
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* const directWriteFactory,
|
||||
ID2D1Factory* const direct2dFactory, IDWriteFontCollection* const fontCollection)
|
||||
{
|
||||
// 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
|
||||
|
||||
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);
|
||||
ComSmartPtr<ID2D1DCRenderTarget> renderTarget;
|
||||
HRESULT hr = direct2dFactory->CreateDCRenderTarget (&d2dRTProp, renderTarget.resetAndGetPointerAddress());
|
||||
|
||||
ComSmartPtr<IDWriteTextLayout> dwTextLayout;
|
||||
|
||||
if (! setupLayout (text, layout.getWidth(), 1.0e7f, renderTarget, directWriteFactory, fontCollection, dwTextLayout))
|
||||
return;
|
||||
|
||||
UINT32 actualLineCount = 0;
|
||||
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());
|
||||
|
||||
for (int i = 0; i < numLines; ++i)
|
||||
{
|
||||
layout.getLine(i).stringRange = Range<int> (lastLocation, (int) lastLocation + dwLineMetrics[i].length);
|
||||
lastLocation += dwLineMetrics[i].length;
|
||||
}
|
||||
}
|
||||
|
||||
void drawToD2DContext (const AttributedString& text, const Rectangle<float>& area, ID2D1RenderTarget* const renderTarget,
|
||||
IDWriteFactory* const directWriteFactory, IDWriteFontCollection* const fontCollection)
|
||||
{
|
||||
ComSmartPtr<IDWriteTextLayout> dwTextLayout;
|
||||
|
||||
if (setupLayout (text, area.getWidth(), area.getHeight(), renderTarget, directWriteFactory, fontCollection, dwTextLayout))
|
||||
{
|
||||
ComSmartPtr<ID2D1SolidColorBrush> d2dBrush;
|
||||
renderTarget->CreateSolidColorBrush (D2D1::ColorF (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 (const Font* const font = text.getAttribute (i)->getFont())
|
||||
if (dynamic_cast<WindowsDirectWriteTypeface*> (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)
|
||||
{
|
||||
#if JUCE_64BIT
|
||||
// There's a mysterious bug in 64-bit Windows that causes garbage floating-point
|
||||
// values to be returned to DrawGlyphRun the first time that it gets used.
|
||||
// In lieu of a better plan, this bodge uses a dummy call to work around this.
|
||||
static bool hasBeenCalled = false;
|
||||
if (! hasBeenCalled)
|
||||
{
|
||||
hasBeenCalled = true;
|
||||
TextLayout dummy;
|
||||
DirectWriteTypeLayout::createLayout (dummy, text, factories->directWriteFactory,
|
||||
factories->d2dFactory, factories->systemFonts);
|
||||
}
|
||||
#endif
|
||||
|
||||
DirectWriteTypeLayout::createLayout (*this, text, factories->directWriteFactory,
|
||||
factories->d2dFactory, factories->systemFonts);
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
(void) text;
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#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)); (void) hr;
|
||||
return getLocalisedName (familyNames);
|
||||
}
|
||||
|
||||
static String getFontFaceName (IDWriteFont* font)
|
||||
{
|
||||
jassert (font != nullptr);
|
||||
ComSmartPtr<IDWriteLocalizedStrings> faceNames;
|
||||
HRESULT hr = font->GetFaceNames (faceNames.resetAndGetPointerAddress());
|
||||
jassert (SUCCEEDED (hr)); (void) hr;
|
||||
|
||||
return getLocalisedName (faceNames);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~Direct2DFactories()
|
||||
{
|
||||
d2dFactory = nullptr; // (need to make sure these are released before deleting the DynamicLibrary objects)
|
||||
directWriteFactory = nullptr;
|
||||
systemFonts = nullptr;
|
||||
}
|
||||
|
||||
ComSmartPtr<ID2D1Factory> d2dFactory;
|
||||
ComSmartPtr<IDWriteFactory> directWriteFactory;
|
||||
ComSmartPtr<IDWriteFontCollection> systemFonts;
|
||||
|
||||
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; }
|
||||
|
||||
private:
|
||||
SharedResourcePointer<Direct2DFactories> factories;
|
||||
ComSmartPtr<IDWriteFontFace> dwFontFace;
|
||||
float unitsToHeightScaleFactor, heightToPointsFactor, ascent;
|
||||
int designUnitsPerEm;
|
||||
AffineTransform pathTransform;
|
||||
|
||||
class PathGeometrySink : public ComBaseClassHelper<IDWriteGeometrySink>
|
||||
{
|
||||
public:
|
||||
PathGeometrySink() : ComBaseClassHelper<IDWriteGeometrySink> (0) {}
|
||||
|
||||
void __stdcall AddBeziers (const D2D1_BEZIER_SEGMENT *beziers, UINT beziersCount)
|
||||
{
|
||||
for (UINT i = 0; i < beziersCount; ++i)
|
||||
path.cubicTo ((float) beziers[i].point1.x, (float) beziers[i].point1.y,
|
||||
(float) beziers[i].point2.x, (float) beziers[i].point2.y,
|
||||
(float) beziers[i].point3.x, (float) beziers[i].point3.y);
|
||||
}
|
||||
|
||||
void __stdcall AddLines (const D2D1_POINT_2F* points, UINT pointsCount)
|
||||
{
|
||||
for (UINT i = 0; i < pointsCount; ++i)
|
||||
path.lineTo ((float) points[i].x,
|
||||
(float) points[i].y);
|
||||
}
|
||||
|
||||
void __stdcall BeginFigure (D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN)
|
||||
{
|
||||
path.startNewSubPath ((float) startPoint.x,
|
||||
(float) startPoint.y);
|
||||
}
|
||||
|
||||
void __stdcall EndFigure (D2D1_FIGURE_END figureEnd)
|
||||
{
|
||||
if (figureEnd == D2D1_FIGURE_END_CLOSED)
|
||||
path.closeSubPath();
|
||||
}
|
||||
|
||||
void __stdcall SetFillMode (D2D1_FILL_MODE fillMode)
|
||||
{
|
||||
path.setUsingNonZeroWinding (fillMode == D2D1_FILL_MODE_WINDING);
|
||||
}
|
||||
|
||||
void __stdcall SetSegmentFlags (D2D1_PATH_SEGMENT) {}
|
||||
JUCE_COMRESULT Close() { return S_OK; }
|
||||
|
||||
Path path;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PathGeometrySink)
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsDirectWriteTypeface)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,644 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/* 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_jassert (sizeof (CharPointer_UTF16::CharType) == sizeof (uint16));
|
||||
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 String();
|
||||
}
|
||||
|
||||
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 String();
|
||||
}
|
||||
}
|
||||
|
||||
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<int16> results (numChars + 1);
|
||||
results[numChars] = -1;
|
||||
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], results[i + 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<int16> results (numChars + 1);
|
||||
results[numChars] = -1;
|
||||
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], results[i + 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 dc, const float height)
|
||||
{
|
||||
HeapBlock<KERNINGPAIR> rawKerning;
|
||||
const DWORD numKPs = GetKerningPairs (dc, 0, 0);
|
||||
rawKerning.calloc (numKPs);
|
||||
GetKerningPairs (dc, numKPs, rawKerning);
|
||||
|
||||
kerningPairs.ensureStorageAllocated ((int) numKPs);
|
||||
|
||||
for (DWORD i = 0; i < numKPs; ++i)
|
||||
{
|
||||
KerningPair kp;
|
||||
kp.glyph1 = getGlyphForChar (dc, rawKerning[i].wFirst);
|
||||
kp.glyph2 = getGlyphForChar (dc, rawKerning[i].wSecond);
|
||||
|
||||
const int standardWidth = getGlyphWidth (dc, kp.glyph1);
|
||||
kp.kerning = (standardWidth + rawKerning[i].iKernAmount) / height;
|
||||
kerningPairs.add (kp);
|
||||
|
||||
kp.glyph2 = -1; // add another entry for the standard width version..
|
||||
kp.kerning = standardWidth / height;
|
||||
kerningPairs.add (kp);
|
||||
}
|
||||
}
|
||||
|
||||
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 dc, const int glyph1, const int glyph2)
|
||||
{
|
||||
KerningPair kp;
|
||||
kp.glyph1 = glyph1;
|
||||
kp.glyph2 = glyph2;
|
||||
int index = kerningPairs.indexOf (kp);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
kp.glyph2 = -1;
|
||||
index = kerningPairs.indexOf (kp);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
kp.glyph2 = -1;
|
||||
kp.kerning = getGlyphWidth (dc, kp.glyph1) / (float) tm.tmHeight;
|
||||
kerningPairs.add (kp);
|
||||
return kp.kerning;
|
||||
}
|
||||
}
|
||||
|
||||
return kerningPairs.getReference (index).kerning;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ScopedPointer<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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue