1
0
Fork 0
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:
Felix Faire 2014-10-29 15:55:23 +00:00
parent fefcf7aca6
commit ff6520a89a
1141 changed files with 438491 additions and 94 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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)
};

View file

@ -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);
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)
};

View file

@ -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;
}

View file

@ -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

View file

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