JUCE should have CoreText support because:
- It is necessary for future Complex Text support in JUCE
- Some fonts don’t display properly using the current text rendering system (Examples: Batang, Playbill, Stencil, Wide Latin)
- It will allow us to create Paths on iOS without needing Edge Tables
I have implemented such support and hope to see it make its way into the tip.
The code below can act as a drop in replacement for juce_mac_Fonts.mm. I eliminated all legacy code to make it shorter and easier to read. This means that the code will only run on OS X 10.5+ and iOS 3.2+.
I figured you would want to do the integration into the existing juce_mac_Fonts.mm yourself since it will require more preprocessor code.
I didn’t change Font::findAllTypefaceNames() to use CoreText since the existing code works across all OSX/iOS versions.
To use this code on iOS you will need to:
- Add #import <CoreText/CoreText.h> to the #if JUCE_IOS in Juce_Mac_NativeIncludes.h
- Add the CoreText framework to your JUCE project
Is this something that can be merged into tip? Is there anything else I can do to speed up that process? thoughts? questions?
// (This file gets included by juce_mac_NativeCode.mm, rather than being
// compiled on its own).
#if JUCE_INCLUDED_FILE
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
END_JUCE_NAMESPACE
@interface NSFont (PrivateHack)
- (NSGlyph) _defaultGlyphForChar: (unichar) theChar;
@end
BEGIN_JUCE_NAMESPACE
#endif
//==============================================================================
class MacTypeface : public Typeface
{
public:
//==============================================================================
MacTypeface (const Font& font)
: Typeface (font.getTypefaceName())
{
const ScopedAutoReleasePool pool;
renderingTransform = CGAffineTransformIdentity;
bool needsItalicTransform = false;
ctFontRef = CTFontCreateWithName(PlatformUtilities::juceStringToCFString (font.getTypefaceName()), 1024, 0);
if (font.isItalic())
{
CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0, 0, kCTFontItalicTrait, kCTFontItalicTrait);
if (newFont == 0)
needsItalicTransform = true; // couldn't find a proper italic version, so fake it with a transform..
else
{
CFRelease (ctFontRef);
ctFontRef = newFont;
}
}
if (font.isBold())
{
CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0, 0, kCTFontBoldTrait, kCTFontBoldTrait);
if (newFont != 0)
{
CFRelease (ctFontRef);
ctFontRef = newFont;
}
}
ascent = std::abs ((float) CTFontGetAscent(ctFontRef));
float totalSize = ascent + std::abs ((float) CTFontGetDescent(ctFontRef));
ascent /= totalSize;
pathTransform = AffineTransform::identity.scale (1.0f / totalSize, 1.0f / totalSize);
if (needsItalicTransform)
{
pathTransform = pathTransform.sheared (-0.15f, 0.0f);
renderingTransform.c = 0.15f;
}
fontRef = CTFontCopyGraphicsFont(ctFontRef, 0);
const int totalHeight = abs (CGFontGetAscent (fontRef)) + abs (CGFontGetDescent (fontRef));
const float ctTotalHeight = abs (CTFontGetAscent (ctFontRef)) + abs (CTFontGetDescent (ctFontRef));
unitsToHeightScaleFactor = 1.0f / ctTotalHeight;
fontHeightToCGSizeFactor = CGFontGetUnitsPerEm (fontRef) / (float) totalHeight;
}
~MacTypeface()
{
if (fontRef != 0)
CGFontRelease (fontRef);
if (ctFontRef != 0)
CFRelease (ctFontRef);
}
float getAscent() const
{
return ascent;
}
float getDescent() const
{
return 1.0f - ascent;
}
float getStringWidth (const String& text)
{
if (ctFontRef == 0 || text.isEmpty())
return 0;
float x = 0;
CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName };
const short zero = 0;
CFNumberRef numberRef = CFNumberCreate(0, kCFNumberShortType, &zero);
CFTypeRef values[] = { ctFontRef, numberRef };
CFDictionaryRef attr = CFDictionaryCreate (NULL, (const void **)&keys, (const void **)&values,
sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFRelease (numberRef);
CFAttributedStringRef attribString = CFAttributedStringCreate (0, PlatformUtilities::juceStringToCFString (text), attr);
CFRelease (attr);
CTLineRef line = CTLineCreateWithAttributedString (attribString);
CFArrayRef runArray = CTLineGetGlyphRuns (line);
for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
{
CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex (runArray, i);
CFIndex length = CTRunGetGlyphCount (run);
HeapBlock <CGSize> advances (length);
CTRunGetAdvances (run, CFRangeMake(0, 0), advances);
for (int j = 0; j < length; ++j)
x += (float) advances[j].width;
}
CFRelease (line);
CFRelease (attribString);
return x * unitsToHeightScaleFactor;
}
void getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array <float>& xOffsets)
{
xOffsets.add (0);
if (ctFontRef == 0 || text.isEmpty())
return;
float x = 0;
CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName };
const short zero = 0;
CFNumberRef numberRef = CFNumberCreate(0, kCFNumberShortType, &zero);
CFTypeRef values[] = { ctFontRef, numberRef };
CFDictionaryRef attr = CFDictionaryCreate (NULL, (const void **)&keys, (const void **)&values,
sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFRelease (numberRef);
CFAttributedStringRef attribString = CFAttributedStringCreate (0, PlatformUtilities::juceStringToCFString (text), attr);
CFRelease (attr);
CTLineRef line = CTLineCreateWithAttributedString (attribString);
CFArrayRef runArray = CTLineGetGlyphRuns (line);
for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
{
CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex (runArray, i);
CFIndex length = CTRunGetGlyphCount (run);
HeapBlock <CGSize> advances (length);
CTRunGetAdvances (run, CFRangeMake(0, 0), advances);
HeapBlock <CGGlyph> glyphs (length);
CTRunGetGlyphs (run, CFRangeMake(0, 0), glyphs);
for (int j = 0; j < length; ++j)
{
x += (float) advances[j].width;
xOffsets.add (x * unitsToHeightScaleFactor);
resultGlyphs.add (glyphs[j]);
}
}
CFRelease (line);
CFRelease (attribString);
}
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform)
{
Path path;
if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty())
return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
path, transform);
return nullptr;
}
bool getOutlineForGlyph (int glyphNumber, Path& path)
{
// we might need to apply a transform to the path, so it mustn't have anything else in it
jassert (path.isEmpty());
CGPathRef pathRef = CTFontCreatePathForGlyph(ctFontRef, (CGGlyph) glyphNumber, 0);
CGPathApply (pathRef, &path, pathApplier);
CFRelease(pathRef);
path.applyTransform (pathTransform);
return true;
}
//==============================================================================
CGFontRef fontRef;
float fontHeightToCGSizeFactor;
CGAffineTransform renderingTransform;
private:
float ascent, unitsToHeightScaleFactor;
CTFontRef ctFontRef;
AffineTransform pathTransform;
static void pathApplier(void* info, const CGPathElement* element)
{
Path* path = (Path*) info;
switch (element->type)
{
case kCGPathElementMoveToPoint: path->startNewSubPath ((float) element->points[0].x, (float) -element->points[0].y); break;
case kCGPathElementAddLineToPoint: path->lineTo ((float) element->points[0].x, (float) -element->points[0].y); break;
case kCGPathElementAddCurveToPoint: path->cubicTo ((float) element->points[0].x, (float) -element->points[0].y,
(float) element->points[1].x, (float) -element->points[1].y,
(float) element->points[2].x, (float) -element->points[2].y); break;
case kCGPathElementAddQuadCurveToPoint: path->quadraticTo ((float) element->points[0].x, (float) -element->points[0].y,
(float) element->points[1].x, (float) -element->points[1].y); break;
case kCGPathElementCloseSubpath: path->closeSubPath(); break;
default: jassertfalse; break;
}
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MacTypeface);
};
const Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)
{
return new MacTypeface (font);
}
//==============================================================================
const StringArray Font::findAllTypefaceNames()
{
StringArray names;
const ScopedAutoReleasePool pool;
#if JUCE_IOS
NSArray* fonts = [UIFont familyNames];
#else
NSArray* fonts = [[NSFontManager sharedFontManager] availableFontFamilies];
#endif
for (unsigned int i = 0; i < [fonts count]; ++i)
names.add (nsStringToJuce ((NSString*) [fonts objectAtIndex: i]));
names.sort (true);
return names;
}
void Font::getPlatformDefaultFontNames (String& defaultSans, String& defaultSerif, String& defaultFixed, String& defaultFallback)
{
#if JUCE_IOS
defaultSans = "Helvetica";
defaultSerif = "Times New Roman";
defaultFixed = "Courier New";
#else
defaultSans = "Lucida Grande";
defaultSerif = "Times New Roman";
defaultFixed = "Monaco";
#endif
defaultFallback = "Arial Unicode MS";
}
#endif