1/*
2  ==============================================================================
3
4   This file is part of the JUCE library.
5   Copyright (c) 2020 - Raw Material Software Limited
6
7   JUCE is an open source library subject to commercial or open-source
8   licensing.
9
10   By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11   Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12
13   End User License Agreement: www.juce.com/juce-6-licence
14   Privacy Policy: www.juce.com/juce-privacy-policy
15
16   Or: You may also use this code under the terms of the GPL v3 (see
17   www.gnu.org/licenses).
18
19   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21   DISCLAIMED.
22
23  ==============================================================================
24*/
25
26namespace juce
27{
28
29static constexpr float referenceFontSize = 1024.0f;
30
31static CTFontRef getCTFontFromTypeface (const Font&);
32
33namespace CoreTextTypeLayout
34{
35    static String findBestAvailableStyle (const Font& font, CGAffineTransform& requiredTransform)
36    {
37        auto availableStyles = Font::findAllTypefaceStyles (font.getTypefaceName());
38        auto style = font.getTypefaceStyle();
39
40        if (! availableStyles.contains (style))
41        {
42            if (font.isItalic())  // Fake-up an italic font if there isn't a real one.
43                requiredTransform = CGAffineTransformMake (1.0f, 0, 0.1f, 1.0f, 0, 0);
44
45            return availableStyles[0];
46        }
47
48        return style;
49    }
50
51    static float getFontTotalHeight (CTFontRef font)
52    {
53        return std::abs ((float) CTFontGetAscent (font))
54             + std::abs ((float) CTFontGetDescent (font));
55    }
56
57    static float getHeightToPointsFactor (CTFontRef font)
58    {
59        return referenceFontSize / getFontTotalHeight (font);
60    }
61
62    static CTFontRef getFontWithPointSize (CTFontRef font, float size)
63    {
64        auto newFont = CTFontCreateCopyWithAttributes (font, size, nullptr, nullptr);
65        CFRelease (font);
66        return newFont;
67    }
68
69    static CTFontRef createCTFont (const Font& font, const float fontSizePoints, CGAffineTransform& transformRequired)
70    {
71        auto cfFontFamily = FontStyleHelpers::getConcreteFamilyName (font).toCFString();
72        auto cfFontStyle = findBestAvailableStyle (font, transformRequired).toCFString();
73        CFStringRef keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute };
74        CFTypeRef values[] = { cfFontFamily, cfFontStyle };
75
76        auto fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys,
77                                                      (const void**) &values,
78                                                      numElementsInArray (keys),
79                                                      &kCFTypeDictionaryKeyCallBacks,
80                                                      &kCFTypeDictionaryValueCallBacks);
81        CFRelease (cfFontStyle);
82
83        auto ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes);
84        CFRelease (fontDescAttributes);
85
86        auto ctFontRef = CTFontCreateWithFontDescriptor (ctFontDescRef, fontSizePoints, nullptr);
87        CFRelease (ctFontDescRef);
88        CFRelease (cfFontFamily);
89
90        return ctFontRef;
91    }
92
93    //==============================================================================
94    struct Advances
95    {
96        Advances (CTRunRef run, CFIndex numGlyphs) : advances (CTRunGetAdvancesPtr (run))
97        {
98            if (advances == nullptr)
99            {
100                local.malloc (numGlyphs);
101                CTRunGetAdvances (run, CFRangeMake (0, 0), local);
102                advances = local;
103            }
104        }
105
106        const CGSize* advances;
107        HeapBlock<CGSize> local;
108    };
109
110    struct Glyphs
111    {
112        Glyphs (CTRunRef run, size_t numGlyphs) : glyphs (CTRunGetGlyphsPtr (run))
113        {
114            if (glyphs == nullptr)
115            {
116                local.malloc (numGlyphs);
117                CTRunGetGlyphs (run, CFRangeMake (0, 0), local);
118                glyphs = local;
119            }
120        }
121
122        const CGGlyph* glyphs;
123        HeapBlock<CGGlyph> local;
124    };
125
126    struct Positions
127    {
128        Positions (CTRunRef run, size_t numGlyphs) : points (CTRunGetPositionsPtr (run))
129        {
130            if (points == nullptr)
131            {
132                local.malloc (numGlyphs);
133                CTRunGetPositions (run, CFRangeMake (0, 0), local);
134                points = local;
135            }
136        }
137
138        const CGPoint* points;
139        HeapBlock<CGPoint> local;
140    };
141
142    struct LineInfo
143    {
144        LineInfo (CTFrameRef frame, CTLineRef line, CFIndex lineIndex)
145        {
146            CTFrameGetLineOrigins (frame, CFRangeMake (lineIndex, 1), &origin);
147            CTLineGetTypographicBounds (line, &ascent,  &descent, &leading);
148        }
149
150        CGPoint origin;
151        CGFloat ascent, descent, leading;
152    };
153
154    static CTFontRef getOrCreateFont (const Font& f)
155    {
156        if (auto ctf = getCTFontFromTypeface (f))
157        {
158            CFRetain (ctf);
159            return ctf;
160        }
161
162        CGAffineTransform transform;
163        return createCTFont (f, referenceFontSize, transform);
164    }
165
166    //==============================================================================
167    static CTTextAlignment getTextAlignment (const AttributedString& text)
168    {
169        switch (text.getJustification().getOnlyHorizontalFlags())
170        {
171           #if defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
172            case Justification::right:                  return kCTTextAlignmentRight;
173            case Justification::horizontallyCentred:    return kCTTextAlignmentCenter;
174            case Justification::horizontallyJustified:  return kCTTextAlignmentJustified;
175            default:                                    return kCTTextAlignmentLeft;
176           #else
177            case Justification::right:                  return kCTRightTextAlignment;
178            case Justification::horizontallyCentred:    return kCTCenterTextAlignment;
179            case Justification::horizontallyJustified:  return kCTJustifiedTextAlignment;
180            default:                                    return kCTLeftTextAlignment;
181           #endif
182        }
183    }
184
185    static CTLineBreakMode getLineBreakMode (const AttributedString& text)
186    {
187        switch (text.getWordWrap())
188        {
189            case AttributedString::none:        return kCTLineBreakByClipping;
190            case AttributedString::byChar:      return kCTLineBreakByCharWrapping;
191            case AttributedString::byWord:
192            default:                            return kCTLineBreakByWordWrapping;
193        }
194    }
195
196    static CTWritingDirection getWritingDirection (const AttributedString& text)
197    {
198        switch (text.getReadingDirection())
199        {
200            case AttributedString::rightToLeft:   return kCTWritingDirectionRightToLeft;
201            case AttributedString::leftToRight:   return kCTWritingDirectionLeftToRight;
202            case AttributedString::natural:
203            default:                              return kCTWritingDirectionNatural;
204        }
205    }
206
207    //==============================================================================
208    static CFAttributedStringRef createCFAttributedString (const AttributedString& text)
209    {
210       #if JUCE_IOS
211        auto rgbColourSpace = CGColorSpaceCreateWithName (kCGColorSpaceSRGB);
212       #endif
213
214        auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0);
215        auto cfText = text.getText().toCFString();
216        CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText);
217        CFRelease (cfText);
218
219        auto numCharacterAttributes = text.getNumAttributes();
220        auto attribStringLen = CFAttributedStringGetLength (attribString);
221
222        for (int i = 0; i < numCharacterAttributes; ++i)
223        {
224            auto& attr = text.getAttribute (i);
225            auto rangeStart = attr.range.getStart();
226
227            if (rangeStart >= attribStringLen)
228                continue;
229
230            auto range = CFRangeMake (rangeStart, jmin (attr.range.getEnd(), (int) attribStringLen) - rangeStart);
231
232            if (auto ctFontRef = getOrCreateFont (attr.font))
233            {
234                ctFontRef = getFontWithPointSize (ctFontRef, attr.font.getHeight() * getHeightToPointsFactor (ctFontRef));
235                CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef);
236
237                if (attr.font.isUnderlined())
238                {
239                    auto underline = kCTUnderlineStyleSingle;
240
241                    auto numberRef = CFNumberCreate (nullptr, kCFNumberIntType, &underline);
242                    CFAttributedStringSetAttribute (attribString, range, kCTUnderlineStyleAttributeName, numberRef);
243                    CFRelease (numberRef);
244                }
245
246                auto extraKerning = attr.font.getExtraKerningFactor();
247
248                if (extraKerning != 0)
249                {
250                    extraKerning *= attr.font.getHeight();
251
252                    auto numberRef = CFNumberCreate (nullptr, kCFNumberFloatType, &extraKerning);
253                    CFAttributedStringSetAttribute (attribString, range, kCTKernAttributeName, numberRef);
254                    CFRelease (numberRef);
255                }
256
257                CFRelease (ctFontRef);
258            }
259
260            {
261                auto col = attr.colour;
262
263               #if JUCE_IOS
264                const CGFloat components[] = { col.getFloatRed(),
265                                               col.getFloatGreen(),
266                                               col.getFloatBlue(),
267                                               col.getFloatAlpha() };
268                auto colour = CGColorCreate (rgbColourSpace, components);
269               #else
270                auto colour = CGColorCreateGenericRGB (col.getFloatRed(),
271                                                       col.getFloatGreen(),
272                                                       col.getFloatBlue(),
273                                                       col.getFloatAlpha());
274               #endif
275
276                CFAttributedStringSetAttribute (attribString, range, kCTForegroundColorAttributeName, colour);
277                CGColorRelease (colour);
278            }
279        }
280
281        // Paragraph Attributes
282        auto ctTextAlignment = getTextAlignment (text);
283        auto ctLineBreakMode = getLineBreakMode (text);
284        auto ctWritingDirection = getWritingDirection (text);
285        CGFloat ctLineSpacing = text.getLineSpacing();
286
287        CTParagraphStyleSetting settings[] =
288        {
289            { kCTParagraphStyleSpecifierAlignment,              sizeof (CTTextAlignment),    &ctTextAlignment },
290            { kCTParagraphStyleSpecifierLineBreakMode,          sizeof (CTLineBreakMode),    &ctLineBreakMode },
291            { kCTParagraphStyleSpecifierBaseWritingDirection,   sizeof (CTWritingDirection), &ctWritingDirection},
292            { kCTParagraphStyleSpecifierLineSpacingAdjustment,  sizeof (CGFloat),            &ctLineSpacing }
293        };
294
295        auto ctParagraphStyleRef = CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings));
296        CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)),
297                                        kCTParagraphStyleAttributeName, ctParagraphStyleRef);
298        CFRelease (ctParagraphStyleRef);
299       #if JUCE_IOS
300        CGColorSpaceRelease (rgbColourSpace);
301       #endif
302        return attribString;
303    }
304
305    static CTFramesetterRef createCTFramesetter (const AttributedString& text)
306    {
307        auto attribString = createCFAttributedString (text);
308        auto framesetter = CTFramesetterCreateWithAttributedString (attribString);
309        CFRelease (attribString);
310
311        return framesetter;
312    }
313
314    static CTFrameRef createCTFrame (CTFramesetterRef framesetter, CGRect bounds)
315    {
316        auto path = CGPathCreateMutable();
317        CGPathAddRect (path, nullptr, bounds);
318
319        auto frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr);
320        CGPathRelease (path);
321
322        return frame;
323    }
324
325    static CTFrameRef createCTFrame (const AttributedString& text, CGRect bounds)
326    {
327        auto framesetter = createCTFramesetter (text);
328        auto frame = createCTFrame (framesetter, bounds);
329        CFRelease (framesetter);
330
331        return frame;
332    }
333
334    static Range<float> getLineVerticalRange (CTFrameRef frame, CFArrayRef lines, int lineIndex)
335    {
336        LineInfo info (frame, (CTLineRef) CFArrayGetValueAtIndex (lines, lineIndex), lineIndex);
337
338        return { (float) (info.origin.y - info.descent),
339                 (float) (info.origin.y + info.ascent) };
340    }
341
342    static float findCTFrameHeight (CTFrameRef frame)
343    {
344        auto lines = CTFrameGetLines (frame);
345        auto numLines = CFArrayGetCount (lines);
346
347        if (numLines == 0)
348            return 0;
349
350        auto range = getLineVerticalRange (frame, lines, 0);
351
352        if (numLines > 1)
353            range = range.getUnionWith (getLineVerticalRange (frame, lines, (int) numLines - 1));
354
355        return range.getLength();
356    }
357
358    static void drawToCGContext (const AttributedString& text, const Rectangle<float>& area,
359                                 const CGContextRef& context, float flipHeight)
360    {
361        auto framesetter = createCTFramesetter (text);
362
363        // Ugly hack to fix a bug in OS X Sierra where the CTFrame needs to be slightly
364        // larger than the font height - otherwise the CTFrame will be invalid
365
366        CFRange fitrange;
367        auto suggestedSingleLineFrameSize =
368            CTFramesetterSuggestFrameSizeWithConstraints (framesetter, CFRangeMake (0, 0), nullptr,
369                                                          CGSizeMake (CGFLOAT_MAX, CGFLOAT_MAX), &fitrange);
370        auto minCTFrameHeight = (float) suggestedSingleLineFrameSize.height;
371
372        auto verticalJustification = text.getJustification().getOnlyVerticalFlags();
373
374        const Rectangle<float> ctFrameArea = [area, minCTFrameHeight, verticalJustification]
375        {
376            if (minCTFrameHeight < area.getHeight())
377                return Rectangle<float> (area);
378
379            if (verticalJustification == Justification::verticallyCentred)
380                return area.withSizeKeepingCentre (area.getWidth(), minCTFrameHeight);
381
382            const Rectangle<float> frameArea = area.withHeight (minCTFrameHeight);
383
384            if (verticalJustification == Justification::bottom)
385                return frameArea.withBottomY (area.getBottom());
386
387            return Rectangle<float> (frameArea);
388        }();
389
390        auto frame = createCTFrame (framesetter, CGRectMake ((CGFloat) ctFrameArea.getX(), flipHeight - (CGFloat) ctFrameArea.getBottom(),
391                                                             (CGFloat) ctFrameArea.getWidth(), (CGFloat) ctFrameArea.getHeight()));
392        CFRelease (framesetter);
393
394        auto textMatrix = CGContextGetTextMatrix (context);
395        CGContextSaveGState (context);
396
397        if (verticalJustification == Justification::verticallyCentred
398         || verticalJustification == Justification::bottom)
399        {
400            auto adjust = ctFrameArea.getHeight() - findCTFrameHeight (frame);
401
402            if (verticalJustification == Justification::verticallyCentred)
403                adjust *= 0.5f;
404
405            CGContextTranslateCTM (context, 0, -adjust);
406        }
407
408        CTFrameDraw (frame, context);
409
410        CGContextRestoreGState (context);
411        CGContextSetTextMatrix (context, textMatrix);
412
413        CFRelease (frame);
414    }
415
416    static void createLayout (TextLayout& glyphLayout, const AttributedString& text)
417    {
418        auto boundsHeight = glyphLayout.getHeight();
419        auto frame = createCTFrame (text, CGRectMake (0, 0, glyphLayout.getWidth(), boundsHeight));
420        auto lines = CTFrameGetLines (frame);
421        auto numLines = CFArrayGetCount (lines);
422
423        glyphLayout.ensureStorageAllocated ((int) numLines);
424
425        for (CFIndex i = 0; i < numLines; ++i)
426        {
427            auto line = (CTLineRef) CFArrayGetValueAtIndex (lines, i);
428            auto runs = CTLineGetGlyphRuns (line);
429            auto numRuns = CFArrayGetCount (runs);
430
431            auto cfrlineStringRange = CTLineGetStringRange (line);
432            auto lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length;
433            Range<int> lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd);
434
435            LineInfo lineInfo (frame, line, i);
436
437            auto glyphLine = std::make_unique<TextLayout::Line> (lineStringRange,
438                                                                 Point<float> ((float) lineInfo.origin.x,
439                                                                               (float) (boundsHeight - lineInfo.origin.y)),
440                                                                 (float) lineInfo.ascent,
441                                                                 (float) lineInfo.descent,
442                                                                 (float) lineInfo.leading,
443                                                                 (int) numRuns);
444
445            for (CFIndex j = 0; j < numRuns; ++j)
446            {
447                auto run = (CTRunRef) CFArrayGetValueAtIndex (runs, j);
448                auto numGlyphs = CTRunGetGlyphCount (run);
449                auto runStringRange = CTRunGetStringRange (run);
450
451                auto glyphRun = new TextLayout::Run (Range<int> ((int) runStringRange.location,
452                                                                 (int) (runStringRange.location + runStringRange.length - 1)),
453                                                     (int) numGlyphs);
454                glyphLine->runs.add (glyphRun);
455
456                CFDictionaryRef runAttributes = CTRunGetAttributes (run);
457
458                CTFontRef ctRunFont;
459                if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void**) &ctRunFont))
460                {
461                    auto cfsFontName = CTFontCopyPostScriptName (ctRunFont);
462                    auto ctFontRef = CTFontCreateWithName (cfsFontName, referenceFontSize, nullptr);
463                    CFRelease (cfsFontName);
464
465                    auto fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef);
466                    CFRelease (ctFontRef);
467
468                    auto cfsFontFamily = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute);
469                    auto cfsFontStyle  = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontStyleNameAttribute);
470
471                    glyphRun->font = Font (String::fromCFString (cfsFontFamily),
472                                           String::fromCFString (cfsFontStyle),
473                                           (float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor));
474
475                    auto isUnderlined = [&]
476                    {
477                        CFNumberRef underlineStyle;
478
479                        if (CFDictionaryGetValueIfPresent (runAttributes, kCTUnderlineStyleAttributeName, (const void**) &underlineStyle))
480                        {
481                            if (CFGetTypeID (underlineStyle) == CFNumberGetTypeID())
482                            {
483                                int value = 0;
484                                CFNumberGetValue (underlineStyle, kCFNumberLongType, (void*) &value);
485
486                                return value != 0;
487                            }
488                        }
489
490                        return false;
491                    }();
492
493                    glyphRun->font.setUnderline (isUnderlined);
494
495                    CFRelease (cfsFontStyle);
496                    CFRelease (cfsFontFamily);
497                }
498
499                CGColorRef cgRunColor;
500
501                if (CFDictionaryGetValueIfPresent (runAttributes, kCTForegroundColorAttributeName, (const void**) &cgRunColor)
502                     && CGColorGetNumberOfComponents (cgRunColor) == 4)
503                {
504                    auto* components = CGColorGetComponents (cgRunColor);
505
506                    glyphRun->colour = Colour::fromFloatRGBA ((float) components[0],
507                                                              (float) components[1],
508                                                              (float) components[2],
509                                                              (float) components[3]);
510                }
511
512                const Glyphs glyphs (run, (size_t) numGlyphs);
513                const Advances advances (run, numGlyphs);
514                const Positions positions (run, (size_t) numGlyphs);
515
516                for (CFIndex k = 0; k < numGlyphs; ++k)
517                    glyphRun->glyphs.add (TextLayout::Glyph (glyphs.glyphs[k], Point<float> ((float) positions.points[k].x,
518                                                                                             (float) positions.points[k].y),
519                                                             (float) advances.advances[k].width));
520            }
521
522            glyphLayout.addLine (std::move (glyphLine));
523        }
524
525        CFRelease (frame);
526    }
527}
528
529
530//==============================================================================
531class OSXTypeface  : public Typeface
532{
533public:
534    OSXTypeface (const Font& font)
535        : Typeface (font.getTypefaceName(), font.getTypefaceStyle()), canBeUsedForLayout (true)
536    {
537        ctFontRef = CoreTextTypeLayout::createCTFont (font, referenceFontSize, renderingTransform);
538
539        if (ctFontRef != nullptr)
540        {
541            fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr);
542            initialiseMetrics();
543        }
544    }
545
546    OSXTypeface (const void* data, size_t dataSize)
547        : Typeface ({}, {}), canBeUsedForLayout (false), dataCopy (data, dataSize)
548    {
549        // We can't use CFDataCreate here as this triggers a false positive in ASAN
550        // so copy the data manually and use CFDataCreateWithBytesNoCopy
551        auto cfData = CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) dataCopy.getData(),
552                                                   (CFIndex) dataCopy.getSize(), kCFAllocatorNull);
553        auto provider = CGDataProviderCreateWithCFData (cfData);
554        CFRelease (cfData);
555
556       #if JUCE_IOS
557        // Workaround for a an obscure iOS bug which can cause the app to dead-lock
558        // when loading custom type faces. See: http://www.openradar.me/18778790 and
559        // http://stackoverflow.com/questions/40242370/app-hangs-in-simulator
560        [UIFont systemFontOfSize: 12];
561       #endif
562
563        fontRef = CGFontCreateWithDataProvider (provider);
564        CGDataProviderRelease (provider);
565
566        if (fontRef != nullptr)
567        {
568           #if JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8
569            if (SystemStats::getOperatingSystemType() >= SystemStats::OperatingSystemType::MacOSX_10_11)
570                canBeUsedForLayout = CTFontManagerRegisterGraphicsFont (fontRef, nullptr);
571           #endif
572
573            ctFontRef = CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr);
574
575            if (ctFontRef != nullptr)
576            {
577                if (auto fontName = CTFontCopyName (ctFontRef, kCTFontFamilyNameKey))
578                {
579                    name = String::fromCFString (fontName);
580                    CFRelease (fontName);
581                }
582
583                if (auto fontStyle = CTFontCopyName (ctFontRef, kCTFontStyleNameKey))
584                {
585                    style = String::fromCFString (fontStyle);
586                    CFRelease (fontStyle);
587                }
588
589                initialiseMetrics();
590            }
591        }
592    }
593
594    void initialiseMetrics()
595    {
596        auto ctAscent  = std::abs ((float) CTFontGetAscent (ctFontRef));
597        auto ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef));
598        auto ctTotalHeight = ctAscent + ctDescent;
599
600        ascent = ctAscent / ctTotalHeight;
601        unitsToHeightScaleFactor = 1.0f / ctTotalHeight;
602        pathTransform = AffineTransform::scale (unitsToHeightScaleFactor);
603
604        fontHeightToPointsFactor = referenceFontSize / ctTotalHeight;
605
606        const short zero = 0;
607        auto numberRef = CFNumberCreate (nullptr, kCFNumberShortType, &zero);
608
609        CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName };
610        CFTypeRef values[] = { ctFontRef, numberRef };
611        attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys,
612                                                   (const void**) &values, numElementsInArray (keys),
613                                                   &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
614        CFRelease (numberRef);
615    }
616
617    ~OSXTypeface() override
618    {
619        if (attributedStringAtts != nullptr)
620            CFRelease (attributedStringAtts);
621
622        if (fontRef != nullptr)
623        {
624           #if JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8
625            if (dataCopy.getSize() != 0)
626                CTFontManagerUnregisterGraphicsFont (fontRef, nullptr);
627           #endif
628
629            CGFontRelease (fontRef);
630        }
631
632        if (ctFontRef != nullptr)
633            CFRelease (ctFontRef);
634    }
635
636    float getAscent() const override                 { return ascent; }
637    float getDescent() const override                { return 1.0f - ascent; }
638    float getHeightToPointsFactor() const override   { return fontHeightToPointsFactor; }
639
640    float getStringWidth (const String& text) override
641    {
642        float x = 0;
643
644        if (ctFontRef != nullptr && text.isNotEmpty())
645        {
646            auto cfText = text.toCFString();
647            auto attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts);
648            CFRelease (cfText);
649
650            auto line = CTLineCreateWithAttributedString (attribString);
651            auto runArray = CTLineGetGlyphRuns (line);
652
653            for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
654            {
655                auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
656                auto length = CTRunGetGlyphCount (run);
657
658                const CoreTextTypeLayout::Advances advances (run, length);
659
660                for (int j = 0; j < length; ++j)
661                    x += (float) advances.advances[j].width;
662            }
663
664            CFRelease (line);
665            CFRelease (attribString);
666
667            x *= unitsToHeightScaleFactor;
668        }
669
670        return x;
671    }
672
673    void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets) override
674    {
675        xOffsets.add (0);
676
677        if (ctFontRef != nullptr && text.isNotEmpty())
678        {
679            float x = 0;
680
681            auto cfText = text.toCFString();
682            auto attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts);
683            CFRelease (cfText);
684
685            auto line = CTLineCreateWithAttributedString (attribString);
686            auto runArray = CTLineGetGlyphRuns (line);
687
688            for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
689            {
690                auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
691                auto length = CTRunGetGlyphCount (run);
692
693                const CoreTextTypeLayout::Advances advances (run, length);
694                const CoreTextTypeLayout::Glyphs glyphs (run, (size_t) length);
695
696                for (int j = 0; j < length; ++j)
697                {
698                    x += (float) advances.advances[j].width;
699                    xOffsets.add (x * unitsToHeightScaleFactor);
700                    resultGlyphs.add (glyphs.glyphs[j]);
701                }
702            }
703
704            CFRelease (line);
705            CFRelease (attribString);
706        }
707    }
708
709    bool getOutlineForGlyph (int glyphNumber, Path& path) override
710    {
711        jassert (path.isEmpty());  // we might need to apply a transform to the path, so this must be empty
712
713        if (auto pathRef = CTFontCreatePathForGlyph (ctFontRef, (CGGlyph) glyphNumber, &renderingTransform))
714        {
715            CGPathApply (pathRef, &path, pathApplier);
716            CFRelease (pathRef);
717
718            if (! pathTransform.isIdentity())
719                path.applyTransform (pathTransform);
720
721            return true;
722        }
723
724        return false;
725    }
726
727    //==============================================================================
728    CGFontRef fontRef = {};
729    CTFontRef ctFontRef = {};
730
731    float fontHeightToPointsFactor = 1.0f;
732    CGAffineTransform renderingTransform = CGAffineTransformIdentity;
733
734    bool canBeUsedForLayout;
735
736private:
737    MemoryBlock dataCopy;
738    CFDictionaryRef attributedStringAtts = {};
739    float ascent = 0, unitsToHeightScaleFactor = 0;
740    AffineTransform pathTransform;
741
742    static void pathApplier (void* info, const CGPathElement* element)
743    {
744        auto& path = *static_cast<Path*> (info);
745        auto* p = element->points;
746
747        switch (element->type)
748        {
749            case kCGPathElementMoveToPoint:         path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break;
750            case kCGPathElementAddLineToPoint:      path.lineTo          ((float) p[0].x, (float) -p[0].y); break;
751            case kCGPathElementAddQuadCurveToPoint: path.quadraticTo     ((float) p[0].x, (float) -p[0].y,
752                                                                          (float) p[1].x, (float) -p[1].y); break;
753            case kCGPathElementAddCurveToPoint:     path.cubicTo         ((float) p[0].x, (float) -p[0].y,
754                                                                          (float) p[1].x, (float) -p[1].y,
755                                                                          (float) p[2].x, (float) -p[2].y); break;
756            case kCGPathElementCloseSubpath:        path.closeSubPath(); break;
757            default:                                jassertfalse; break;
758        }
759    }
760
761    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXTypeface)
762};
763
764CTFontRef getCTFontFromTypeface (const Font& f)
765{
766    if (auto* tf = dynamic_cast<OSXTypeface*> (f.getTypeface()))
767        return tf->ctFontRef;
768
769    return {};
770}
771
772StringArray Font::findAllTypefaceNames()
773{
774    StringArray names;
775
776   #if JUCE_MAC
777    // CTFontManager only exists on OS X 10.6 and later, it does not exist on iOS
778    auto fontFamilyArray = CTFontManagerCopyAvailableFontFamilyNames();
779
780    for (CFIndex i = 0; i < CFArrayGetCount (fontFamilyArray); ++i)
781    {
782        auto family = String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (fontFamilyArray, i));
783
784        if (! family.startsWithChar ('.')) // ignore fonts that start with a '.'
785            names.addIfNotAlreadyThere (family);
786    }
787
788    CFRelease (fontFamilyArray);
789   #else
790    auto fontCollectionRef = CTFontCollectionCreateFromAvailableFonts (nullptr);
791    auto fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef);
792    CFRelease (fontCollectionRef);
793
794    for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i)
795    {
796        auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i);
797        auto cfsFontFamily = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontFamilyNameAttribute);
798
799        names.addIfNotAlreadyThere (String::fromCFString (cfsFontFamily));
800
801        CFRelease (cfsFontFamily);
802    }
803
804    CFRelease (fontDescriptorArray);
805   #endif
806
807    names.sort (true);
808    return names;
809}
810
811StringArray Font::findAllTypefaceStyles (const String& family)
812{
813    if (FontStyleHelpers::isPlaceholderFamilyName (family))
814        return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));
815
816    StringArray results;
817
818    auto cfsFontFamily = family.toCFString();
819    CFStringRef keys[] = { kCTFontFamilyNameAttribute };
820    CFTypeRef values[] = { cfsFontFamily };
821
822    auto fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys),
823                                                  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
824    CFRelease (cfsFontFamily);
825
826    auto ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes);
827    CFRelease (fontDescAttributes);
828
829    auto fontFamilyArray = CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks);
830    CFRelease (ctFontDescRef);
831
832    auto fontCollectionRef = CTFontCollectionCreateWithFontDescriptors (fontFamilyArray, nullptr);
833    CFRelease (fontFamilyArray);
834
835    auto fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef);
836    CFRelease (fontCollectionRef);
837
838    if (fontDescriptorArray != nullptr)
839    {
840        for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i)
841        {
842            auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i);
843            auto cfsFontStyle = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontStyleNameAttribute);
844            results.add (String::fromCFString (cfsFontStyle));
845            CFRelease (cfsFontStyle);
846        }
847
848        CFRelease (fontDescriptorArray);
849    }
850
851    return results;
852}
853
854
855//==============================================================================
856Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)                  { return *new OSXTypeface (font); }
857Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size)     { return *new OSXTypeface (data, size); }
858
859void Typeface::scanFolderForFonts (const File& folder)
860{
861    for (auto& file : folder.findChildFiles (File::findFiles, false, "*.otf;*.ttf"))
862    {
863        if (auto urlref = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, file.getFullPathName().toCFString(), kCFURLPOSIXPathStyle, true))
864        {
865            CTFontManagerRegisterFontsForURL (urlref, kCTFontManagerScopeProcess, nullptr);
866            CFRelease (urlref);
867        }
868    }
869}
870
871struct DefaultFontNames
872{
873   #if JUCE_IOS
874    String defaultSans  { "Helvetica" },
875           defaultSerif { "Times New Roman" },
876           defaultFixed { "Courier New" };
877   #else
878    String defaultSans  { "Lucida Grande" },
879           defaultSerif { "Times New Roman" },
880           defaultFixed { "Menlo" };
881   #endif
882};
883
884Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
885{
886    static DefaultFontNames defaultNames;
887
888    auto newFont = font;
889    auto& faceName = font.getTypefaceName();
890
891    if (faceName == getDefaultSansSerifFontName())       newFont.setTypefaceName (defaultNames.defaultSans);
892    else if (faceName == getDefaultSerifFontName())      newFont.setTypefaceName (defaultNames.defaultSerif);
893    else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed);
894
895    if (font.getTypefaceStyle() == getDefaultStyle())
896        newFont.setTypefaceStyle ("Regular");
897
898    return Typeface::createSystemTypefaceFor (newFont);
899}
900
901static bool canAllTypefacesBeUsedInLayout (const AttributedString& text)
902{
903    auto numCharacterAttributes = text.getNumAttributes();
904
905    for (int i = 0; i < numCharacterAttributes; ++i)
906    {
907        if (auto tf = dynamic_cast<OSXTypeface*> (text.getAttribute(i).font.getTypeface()))
908            if (tf->canBeUsedForLayout)
909                continue;
910
911        return false;
912    }
913
914    return true;
915}
916
917bool TextLayout::createNativeLayout (const AttributedString& text)
918{
919    if (canAllTypefacesBeUsedInLayout (text))
920    {
921        CoreTextTypeLayout::createLayout (*this, text);
922        return true;
923    }
924
925    return false;
926}
927
928} // namespace juce
929