/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2020 - Raw Material Software Limited

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 6 End-User License
   Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).

   End User License Agreement: www.juce.com/juce-6-licence
   Privacy Policy: www.juce.com/juce-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

static constexpr float referenceFontSize = 1024.0f;

static CTFontRef getCTFontFromTypeface (const Font&);

namespace CoreTextTypeLayout
{
    static String findBestAvailableStyle (const Font& font, CGAffineTransform& requiredTransform)
    {
        auto availableStyles = Font::findAllTypefaceStyles (font.getTypefaceName());
        auto style = font.getTypefaceStyle();

        if (! availableStyles.contains (style))
        {
            if (font.isItalic())  // Fake-up an italic font if there isn't a real one.
                requiredTransform = CGAffineTransformMake (1.0f, 0, 0.1f, 1.0f, 0, 0);

            return availableStyles[0];
        }

        return style;
    }

    static float getFontTotalHeight (CTFontRef font)
    {
        return std::abs ((float) CTFontGetAscent (font))
             + std::abs ((float) CTFontGetDescent (font));
    }

    static float getHeightToPointsFactor (CTFontRef font)
    {
        return referenceFontSize / getFontTotalHeight (font);
    }

    static CTFontRef getFontWithPointSize (CTFontRef font, float size)
    {
        auto newFont = CTFontCreateCopyWithAttributes (font, size, nullptr, nullptr);
        CFRelease (font);
        return newFont;
    }

    static CTFontRef createCTFont (const Font& font, const float fontSizePoints, CGAffineTransform& transformRequired)
    {
        auto cfFontFamily = FontStyleHelpers::getConcreteFamilyName (font).toCFString();
        auto cfFontStyle = findBestAvailableStyle (font, transformRequired).toCFString();
        CFStringRef keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute };
        CFTypeRef values[] = { cfFontFamily, cfFontStyle };

        auto fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys,
                                                      (const void**) &values,
                                                      numElementsInArray (keys),
                                                      &kCFTypeDictionaryKeyCallBacks,
                                                      &kCFTypeDictionaryValueCallBacks);
        CFRelease (cfFontStyle);

        auto ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes);
        CFRelease (fontDescAttributes);

        auto ctFontRef = CTFontCreateWithFontDescriptor (ctFontDescRef, fontSizePoints, nullptr);
        CFRelease (ctFontDescRef);
        CFRelease (cfFontFamily);

        return ctFontRef;
    }

    //==============================================================================
    struct Advances
    {
        Advances (CTRunRef run, CFIndex numGlyphs) : advances (CTRunGetAdvancesPtr (run))
        {
            if (advances == nullptr)
            {
                local.malloc (numGlyphs);
                CTRunGetAdvances (run, CFRangeMake (0, 0), local);
                advances = local;
            }
        }

        const CGSize* advances;
        HeapBlock<CGSize> local;
    };

    struct Glyphs
    {
        Glyphs (CTRunRef run, size_t numGlyphs) : glyphs (CTRunGetGlyphsPtr (run))
        {
            if (glyphs == nullptr)
            {
                local.malloc (numGlyphs);
                CTRunGetGlyphs (run, CFRangeMake (0, 0), local);
                glyphs = local;
            }
        }

        const CGGlyph* glyphs;
        HeapBlock<CGGlyph> local;
    };

    struct Positions
    {
        Positions (CTRunRef run, size_t numGlyphs) : points (CTRunGetPositionsPtr (run))
        {
            if (points == nullptr)
            {
                local.malloc (numGlyphs);
                CTRunGetPositions (run, CFRangeMake (0, 0), local);
                points = local;
            }
        }

        const CGPoint* points;
        HeapBlock<CGPoint> local;
    };

    struct LineInfo
    {
        LineInfo (CTFrameRef frame, CTLineRef line, CFIndex lineIndex)
        {
            CTFrameGetLineOrigins (frame, CFRangeMake (lineIndex, 1), &origin);
            CTLineGetTypographicBounds (line, &ascent,  &descent, &leading);
        }

        CGPoint origin;
        CGFloat ascent, descent, leading;
    };

    static CTFontRef getOrCreateFont (const Font& f)
    {
        if (auto ctf = getCTFontFromTypeface (f))
        {
            CFRetain (ctf);
            return ctf;
        }

        CGAffineTransform transform;
        return createCTFont (f, referenceFontSize, transform);
    }

    //==============================================================================
    static CTTextAlignment getTextAlignment (const AttributedString& text)
    {
        switch (text.getJustification().getOnlyHorizontalFlags())
        {
           #if defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
            case Justification::right:                  return kCTTextAlignmentRight;
            case Justification::horizontallyCentred:    return kCTTextAlignmentCenter;
            case Justification::horizontallyJustified:  return kCTTextAlignmentJustified;
            default:                                    return kCTTextAlignmentLeft;
           #else
            case Justification::right:                  return kCTRightTextAlignment;
            case Justification::horizontallyCentred:    return kCTCenterTextAlignment;
            case Justification::horizontallyJustified:  return kCTJustifiedTextAlignment;
            default:                                    return kCTLeftTextAlignment;
           #endif
        }
    }

    static CTLineBreakMode getLineBreakMode (const AttributedString& text)
    {
        switch (text.getWordWrap())
        {
            case AttributedString::none:        return kCTLineBreakByClipping;
            case AttributedString::byChar:      return kCTLineBreakByCharWrapping;
            case AttributedString::byWord:
            default:                            return kCTLineBreakByWordWrapping;
        }
    }

    static CTWritingDirection getWritingDirection (const AttributedString& text)
    {
        switch (text.getReadingDirection())
        {
            case AttributedString::rightToLeft:   return kCTWritingDirectionRightToLeft;
            case AttributedString::leftToRight:   return kCTWritingDirectionLeftToRight;
            case AttributedString::natural:
            default:                              return kCTWritingDirectionNatural;
        }
    }

    //==============================================================================
    static CFAttributedStringRef createCFAttributedString (const AttributedString& text)
    {
       #if JUCE_IOS
        auto rgbColourSpace = CGColorSpaceCreateWithName (kCGColorSpaceSRGB);
       #endif

        auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0);
        auto cfText = text.getText().toCFString();
        CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText);
        CFRelease (cfText);

        auto numCharacterAttributes = text.getNumAttributes();
        auto attribStringLen = CFAttributedStringGetLength (attribString);

        for (int i = 0; i < numCharacterAttributes; ++i)
        {
            auto& attr = text.getAttribute (i);
            auto rangeStart = attr.range.getStart();

            if (rangeStart >= attribStringLen)
                continue;

            auto range = CFRangeMake (rangeStart, jmin (attr.range.getEnd(), (int) attribStringLen) - rangeStart);

            if (auto ctFontRef = getOrCreateFont (attr.font))
            {
                ctFontRef = getFontWithPointSize (ctFontRef, attr.font.getHeight() * getHeightToPointsFactor (ctFontRef));
                CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef);

                if (attr.font.isUnderlined())
                {
                    auto underline = kCTUnderlineStyleSingle;

                    auto numberRef = CFNumberCreate (nullptr, kCFNumberIntType, &underline);
                    CFAttributedStringSetAttribute (attribString, range, kCTUnderlineStyleAttributeName, numberRef);
                    CFRelease (numberRef);
                }

                auto extraKerning = attr.font.getExtraKerningFactor();

                if (extraKerning != 0)
                {
                    extraKerning *= attr.font.getHeight();

                    auto numberRef = CFNumberCreate (nullptr, kCFNumberFloatType, &extraKerning);
                    CFAttributedStringSetAttribute (attribString, range, kCTKernAttributeName, numberRef);
                    CFRelease (numberRef);
                }

                CFRelease (ctFontRef);
            }

            {
                auto col = attr.colour;

               #if JUCE_IOS
                const CGFloat components[] = { col.getFloatRed(),
                                               col.getFloatGreen(),
                                               col.getFloatBlue(),
                                               col.getFloatAlpha() };
                auto colour = CGColorCreate (rgbColourSpace, components);
               #else
                auto colour = CGColorCreateGenericRGB (col.getFloatRed(),
                                                       col.getFloatGreen(),
                                                       col.getFloatBlue(),
                                                       col.getFloatAlpha());
               #endif

                CFAttributedStringSetAttribute (attribString, range, kCTForegroundColorAttributeName, colour);
                CGColorRelease (colour);
            }
        }

        // Paragraph Attributes
        auto ctTextAlignment = getTextAlignment (text);
        auto ctLineBreakMode = getLineBreakMode (text);
        auto ctWritingDirection = getWritingDirection (text);
        CGFloat ctLineSpacing = text.getLineSpacing();

        CTParagraphStyleSetting settings[] =
        {
            { kCTParagraphStyleSpecifierAlignment,              sizeof (CTTextAlignment),    &ctTextAlignment },
            { kCTParagraphStyleSpecifierLineBreakMode,          sizeof (CTLineBreakMode),    &ctLineBreakMode },
            { kCTParagraphStyleSpecifierBaseWritingDirection,   sizeof (CTWritingDirection), &ctWritingDirection},
            { kCTParagraphStyleSpecifierLineSpacingAdjustment,  sizeof (CGFloat),            &ctLineSpacing }
        };

        auto ctParagraphStyleRef = CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings));
        CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)),
                                        kCTParagraphStyleAttributeName, ctParagraphStyleRef);
        CFRelease (ctParagraphStyleRef);
       #if JUCE_IOS
        CGColorSpaceRelease (rgbColourSpace);
       #endif
        return attribString;
    }

    static CTFramesetterRef createCTFramesetter (const AttributedString& text)
    {
        auto attribString = createCFAttributedString (text);
        auto framesetter = CTFramesetterCreateWithAttributedString (attribString);
        CFRelease (attribString);

        return framesetter;
    }

    static CTFrameRef createCTFrame (CTFramesetterRef framesetter, CGRect bounds)
    {
        auto path = CGPathCreateMutable();
        CGPathAddRect (path, nullptr, bounds);

        auto frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr);
        CGPathRelease (path);

        return frame;
    }

    static CTFrameRef createCTFrame (const AttributedString& text, CGRect bounds)
    {
        auto framesetter = createCTFramesetter (text);
        auto frame = createCTFrame (framesetter, bounds);
        CFRelease (framesetter);

        return frame;
    }

    static Range<float> getLineVerticalRange (CTFrameRef frame, CFArrayRef lines, int lineIndex)
    {
        LineInfo info (frame, (CTLineRef) CFArrayGetValueAtIndex (lines, lineIndex), lineIndex);

        return { (float) (info.origin.y - info.descent),
                 (float) (info.origin.y + info.ascent) };
    }

    static float findCTFrameHeight (CTFrameRef frame)
    {
        auto lines = CTFrameGetLines (frame);
        auto numLines = CFArrayGetCount (lines);

        if (numLines == 0)
            return 0;

        auto range = getLineVerticalRange (frame, lines, 0);

        if (numLines > 1)
            range = range.getUnionWith (getLineVerticalRange (frame, lines, (int) numLines - 1));

        return range.getLength();
    }

    static void drawToCGContext (const AttributedString& text, const Rectangle<float>& area,
                                 const CGContextRef& context, float flipHeight)
    {
        auto framesetter = createCTFramesetter (text);

        // Ugly hack to fix a bug in OS X Sierra where the CTFrame needs to be slightly
        // larger than the font height - otherwise the CTFrame will be invalid

        CFRange fitrange;
        auto suggestedSingleLineFrameSize =
            CTFramesetterSuggestFrameSizeWithConstraints (framesetter, CFRangeMake (0, 0), nullptr,
                                                          CGSizeMake (CGFLOAT_MAX, CGFLOAT_MAX), &fitrange);
        auto minCTFrameHeight = (float) suggestedSingleLineFrameSize.height;

        auto verticalJustification = text.getJustification().getOnlyVerticalFlags();

        auto ctFrameArea = [area, minCTFrameHeight, verticalJustification]
        {
            if (minCTFrameHeight < area.getHeight())
                return area;

            if (verticalJustification == Justification::verticallyCentred)
                return area.withSizeKeepingCentre (area.getWidth(), minCTFrameHeight);

            auto frameArea = area.withHeight (minCTFrameHeight);

            if (verticalJustification == Justification::bottom)
                return frameArea.withBottomY (area.getBottom());

            return frameArea;
        }();

        auto frame = createCTFrame (framesetter, CGRectMake ((CGFloat) ctFrameArea.getX(), flipHeight - (CGFloat) ctFrameArea.getBottom(),
                                                             (CGFloat) ctFrameArea.getWidth(), (CGFloat) ctFrameArea.getHeight()));
        CFRelease (framesetter);

        auto textMatrix = CGContextGetTextMatrix (context);
        CGContextSaveGState (context);

        if (verticalJustification == Justification::verticallyCentred
         || verticalJustification == Justification::bottom)
        {
            auto adjust = ctFrameArea.getHeight() - findCTFrameHeight (frame);

            if (verticalJustification == Justification::verticallyCentred)
                adjust *= 0.5f;

            CGContextTranslateCTM (context, 0, -adjust);
        }

        CTFrameDraw (frame, context);

        CGContextRestoreGState (context);
        CGContextSetTextMatrix (context, textMatrix);

        CFRelease (frame);
    }

    static void createLayout (TextLayout& glyphLayout, const AttributedString& text)
    {
        auto boundsHeight = glyphLayout.getHeight();
        auto frame = createCTFrame (text, CGRectMake (0, 0, glyphLayout.getWidth(), boundsHeight));
        auto lines = CTFrameGetLines (frame);
        auto numLines = CFArrayGetCount (lines);

        glyphLayout.ensureStorageAllocated ((int) numLines);

        for (CFIndex i = 0; i < numLines; ++i)
        {
            auto line = (CTLineRef) CFArrayGetValueAtIndex (lines, i);
            auto runs = CTLineGetGlyphRuns (line);
            auto numRuns = CFArrayGetCount (runs);

            auto cfrlineStringRange = CTLineGetStringRange (line);
            auto lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length;
            Range<int> lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd);

            LineInfo lineInfo (frame, line, i);

            auto glyphLine = std::make_unique<TextLayout::Line> (lineStringRange,
                                                                 Point<float> ((float) lineInfo.origin.x,
                                                                               (float) (boundsHeight - lineInfo.origin.y)),
                                                                 (float) lineInfo.ascent,
                                                                 (float) lineInfo.descent,
                                                                 (float) lineInfo.leading,
                                                                 (int) numRuns);

            for (CFIndex j = 0; j < numRuns; ++j)
            {
                auto run = (CTRunRef) CFArrayGetValueAtIndex (runs, j);
                auto numGlyphs = CTRunGetGlyphCount (run);
                auto runStringRange = CTRunGetStringRange (run);

                auto glyphRun = new TextLayout::Run (Range<int> ((int) runStringRange.location,
                                                                 (int) (runStringRange.location + runStringRange.length - 1)),
                                                     (int) numGlyphs);
                glyphLine->runs.add (glyphRun);

                CFDictionaryRef runAttributes = CTRunGetAttributes (run);

                CTFontRef ctRunFont;
                if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void**) &ctRunFont))
                {
                    auto cfsFontName = CTFontCopyPostScriptName (ctRunFont);
                    auto ctFontRef = CTFontCreateWithName (cfsFontName, referenceFontSize, nullptr);
                    CFRelease (cfsFontName);

                    auto fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef);
                    CFRelease (ctFontRef);

                    auto cfsFontFamily = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute);
                    auto cfsFontStyle  = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontStyleNameAttribute);

                    glyphRun->font = Font (String::fromCFString (cfsFontFamily),
                                           String::fromCFString (cfsFontStyle),
                                           (float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor));

                    auto isUnderlined = [&]
                    {
                        CFNumberRef underlineStyle;

                        if (CFDictionaryGetValueIfPresent (runAttributes, kCTUnderlineStyleAttributeName, (const void**) &underlineStyle))
                        {
                            if (CFGetTypeID (underlineStyle) == CFNumberGetTypeID())
                            {
                                int value = 0;
                                CFNumberGetValue (underlineStyle, kCFNumberLongType, (void*) &value);

                                return value != 0;
                            }
                        }

                        return false;
                    }();

                    glyphRun->font.setUnderline (isUnderlined);

                    CFRelease (cfsFontStyle);
                    CFRelease (cfsFontFamily);
                }

                CGColorRef cgRunColor;

                if (CFDictionaryGetValueIfPresent (runAttributes, kCTForegroundColorAttributeName, (const void**) &cgRunColor)
                     && CGColorGetNumberOfComponents (cgRunColor) == 4)
                {
                    auto* components = CGColorGetComponents (cgRunColor);

                    glyphRun->colour = Colour::fromFloatRGBA ((float) components[0],
                                                              (float) components[1],
                                                              (float) components[2],
                                                              (float) components[3]);
                }

                const Glyphs glyphs (run, (size_t) numGlyphs);
                const Advances advances (run, numGlyphs);
                const Positions positions (run, (size_t) numGlyphs);

                for (CFIndex k = 0; k < numGlyphs; ++k)
                    glyphRun->glyphs.add (TextLayout::Glyph (glyphs.glyphs[k], Point<float> ((float) positions.points[k].x,
                                                                                             (float) positions.points[k].y),
                                                             (float) advances.advances[k].width));
            }

            glyphLayout.addLine (std::move (glyphLine));
        }

        CFRelease (frame);
    }
}


//==============================================================================
class OSXTypeface  : public Typeface
{
public:
    OSXTypeface (const Font& font)
        : Typeface (font.getTypefaceName(), font.getTypefaceStyle()), canBeUsedForLayout (true)
    {
        ctFontRef = CoreTextTypeLayout::createCTFont (font, referenceFontSize, renderingTransform);

        if (ctFontRef != nullptr)
        {
            fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr);
            initialiseMetrics();
        }
    }

    OSXTypeface (const void* data, size_t dataSize)
        : Typeface ({}, {}), canBeUsedForLayout (false), dataCopy (data, dataSize)
    {
        // We can't use CFDataCreate here as this triggers a false positive in ASAN
        // so copy the data manually and use CFDataCreateWithBytesNoCopy
        auto cfData = CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, (const UInt8*) dataCopy.getData(),
                                                   (CFIndex) dataCopy.getSize(), kCFAllocatorNull);
        auto provider = CGDataProviderCreateWithCFData (cfData);
        CFRelease (cfData);

       #if JUCE_IOS
        // Workaround for a an obscure iOS bug which can cause the app to dead-lock
        // when loading custom type faces. See: http://www.openradar.me/18778790 and
        // http://stackoverflow.com/questions/40242370/app-hangs-in-simulator
        [UIFont systemFontOfSize: 12];
       #endif

        fontRef = CGFontCreateWithDataProvider (provider);
        CGDataProviderRelease (provider);

        if (fontRef != nullptr)
        {
           #if JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8
            if (SystemStats::getOperatingSystemType() >= SystemStats::OperatingSystemType::MacOSX_10_11)
                canBeUsedForLayout = CTFontManagerRegisterGraphicsFont (fontRef, nullptr);
           #endif

            ctFontRef = CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr);

            if (ctFontRef != nullptr)
            {
                if (auto fontName = CTFontCopyName (ctFontRef, kCTFontFamilyNameKey))
                {
                    name = String::fromCFString (fontName);
                    CFRelease (fontName);
                }

                if (auto fontStyle = CTFontCopyName (ctFontRef, kCTFontStyleNameKey))
                {
                    style = String::fromCFString (fontStyle);
                    CFRelease (fontStyle);
                }

                initialiseMetrics();
            }
        }
    }

    void initialiseMetrics()
    {
        auto ctAscent  = std::abs ((float) CTFontGetAscent (ctFontRef));
        auto ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef));
        auto ctTotalHeight = ctAscent + ctDescent;

        ascent = ctAscent / ctTotalHeight;
        unitsToHeightScaleFactor = 1.0f / ctTotalHeight;
        pathTransform = AffineTransform::scale (unitsToHeightScaleFactor);

        fontHeightToPointsFactor = referenceFontSize / ctTotalHeight;

        const short zero = 0;
        auto numberRef = CFNumberCreate (nullptr, kCFNumberShortType, &zero);

        CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName };
        CFTypeRef values[] = { ctFontRef, numberRef };
        attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys,
                                                   (const void**) &values, numElementsInArray (keys),
                                                   &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        CFRelease (numberRef);
    }

    ~OSXTypeface() override
    {
        if (attributedStringAtts != nullptr)
            CFRelease (attributedStringAtts);

        if (fontRef != nullptr)
        {
           #if JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8
            if (dataCopy.getSize() != 0)
                CTFontManagerUnregisterGraphicsFont (fontRef, nullptr);
           #endif

            CGFontRelease (fontRef);
        }

        if (ctFontRef != nullptr)
            CFRelease (ctFontRef);
    }

    float getAscent() const override                 { return ascent; }
    float getDescent() const override                { return 1.0f - ascent; }
    float getHeightToPointsFactor() const override   { return fontHeightToPointsFactor; }

    float getStringWidth (const String& text) override
    {
        float x = 0;

        if (ctFontRef != nullptr && text.isNotEmpty())
        {
            auto cfText = text.toCFString();
            auto attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts);
            CFRelease (cfText);

            auto line = CTLineCreateWithAttributedString (attribString);
            auto runArray = CTLineGetGlyphRuns (line);

            for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
            {
                auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
                auto length = CTRunGetGlyphCount (run);

                const CoreTextTypeLayout::Advances advances (run, length);

                for (int j = 0; j < length; ++j)
                    x += (float) advances.advances[j].width;
            }

            CFRelease (line);
            CFRelease (attribString);

            x *= unitsToHeightScaleFactor;
        }

        return x;
    }

    void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets) override
    {
        xOffsets.add (0);

        if (ctFontRef != nullptr && text.isNotEmpty())
        {
            float x = 0;

            auto cfText = text.toCFString();
            auto attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts);
            CFRelease (cfText);

            auto line = CTLineCreateWithAttributedString (attribString);
            auto runArray = CTLineGetGlyphRuns (line);

            for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
            {
                auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
                auto length = CTRunGetGlyphCount (run);

                const CoreTextTypeLayout::Advances advances (run, length);
                const CoreTextTypeLayout::Glyphs glyphs (run, (size_t) length);

                for (int j = 0; j < length; ++j)
                {
                    x += (float) advances.advances[j].width;
                    xOffsets.add (x * unitsToHeightScaleFactor);
                    resultGlyphs.add (glyphs.glyphs[j]);
                }
            }

            CFRelease (line);
            CFRelease (attribString);
        }
    }

    bool getOutlineForGlyph (int glyphNumber, Path& path) override
    {
        jassert (path.isEmpty());  // we might need to apply a transform to the path, so this must be empty

        if (auto pathRef = CTFontCreatePathForGlyph (ctFontRef, (CGGlyph) glyphNumber, &renderingTransform))
        {
            CGPathApply (pathRef, &path, pathApplier);
            CFRelease (pathRef);

            if (! pathTransform.isIdentity())
                path.applyTransform (pathTransform);

            return true;
        }

        return false;
    }

    //==============================================================================
    CGFontRef fontRef = {};
    CTFontRef ctFontRef = {};

    float fontHeightToPointsFactor = 1.0f;
    CGAffineTransform renderingTransform = CGAffineTransformIdentity;

    bool canBeUsedForLayout;

private:
    MemoryBlock dataCopy;
    CFDictionaryRef attributedStringAtts = {};
    float ascent = 0, unitsToHeightScaleFactor = 0;
    AffineTransform pathTransform;

    static void pathApplier (void* info, const CGPathElement* element)
    {
        auto& path = *static_cast<Path*> (info);
        auto* p = element->points;

        switch (element->type)
        {
            case kCGPathElementMoveToPoint:         path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break;
            case kCGPathElementAddLineToPoint:      path.lineTo          ((float) p[0].x, (float) -p[0].y); break;
            case kCGPathElementAddQuadCurveToPoint: path.quadraticTo     ((float) p[0].x, (float) -p[0].y,
                                                                          (float) p[1].x, (float) -p[1].y); break;
            case kCGPathElementAddCurveToPoint:     path.cubicTo         ((float) p[0].x, (float) -p[0].y,
                                                                          (float) p[1].x, (float) -p[1].y,
                                                                          (float) p[2].x, (float) -p[2].y); break;
            case kCGPathElementCloseSubpath:        path.closeSubPath(); break;
            default:                                jassertfalse; break;
        }
    }

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXTypeface)
};

CTFontRef getCTFontFromTypeface (const Font& f)
{
    if (auto* tf = dynamic_cast<OSXTypeface*> (f.getTypeface()))
        return tf->ctFontRef;

    return {};
}

StringArray Font::findAllTypefaceNames()
{
    StringArray names;

   #if JUCE_MAC
    // CTFontManager only exists on OS X 10.6 and later, it does not exist on iOS
    auto fontFamilyArray = CTFontManagerCopyAvailableFontFamilyNames();

    for (CFIndex i = 0; i < CFArrayGetCount (fontFamilyArray); ++i)
    {
        auto family = String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (fontFamilyArray, i));

        if (! family.startsWithChar ('.')) // ignore fonts that start with a '.'
            names.addIfNotAlreadyThere (family);
    }

    CFRelease (fontFamilyArray);
   #else
    auto fontCollectionRef = CTFontCollectionCreateFromAvailableFonts (nullptr);
    auto fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef);
    CFRelease (fontCollectionRef);

    for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i)
    {
        auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i);
        auto cfsFontFamily = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontFamilyNameAttribute);

        names.addIfNotAlreadyThere (String::fromCFString (cfsFontFamily));

        CFRelease (cfsFontFamily);
    }

    CFRelease (fontDescriptorArray);
   #endif

    names.sort (true);
    return names;
}

StringArray Font::findAllTypefaceStyles (const String& family)
{
    if (FontStyleHelpers::isPlaceholderFamilyName (family))
        return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family));

    StringArray results;

    auto cfsFontFamily = family.toCFString();
    CFStringRef keys[] = { kCTFontFamilyNameAttribute };
    CFTypeRef values[] = { cfsFontFamily };

    auto fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys),
                                                  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFRelease (cfsFontFamily);

    auto ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes);
    CFRelease (fontDescAttributes);

    auto fontFamilyArray = CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks);
    CFRelease (ctFontDescRef);

    auto fontCollectionRef = CTFontCollectionCreateWithFontDescriptors (fontFamilyArray, nullptr);
    CFRelease (fontFamilyArray);

    auto fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef);
    CFRelease (fontCollectionRef);

    if (fontDescriptorArray != nullptr)
    {
        for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i)
        {
            auto ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i);
            auto cfsFontStyle = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontStyleNameAttribute);
            results.add (String::fromCFString (cfsFontStyle));
            CFRelease (cfsFontStyle);
        }

        CFRelease (fontDescriptorArray);
    }

    return results;
}


//==============================================================================
Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font)                  { return *new OSXTypeface (font); }
Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size)     { return *new OSXTypeface (data, size); }

void Typeface::scanFolderForFonts (const File& folder)
{
    for (auto& file : folder.findChildFiles (File::findFiles, false, "*.otf;*.ttf"))
    {
        if (auto urlref = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, file.getFullPathName().toCFString(), kCFURLPOSIXPathStyle, true))
        {
            CTFontManagerRegisterFontsForURL (urlref, kCTFontManagerScopeProcess, nullptr);
            CFRelease (urlref);
        }
    }
}

struct DefaultFontNames
{
   #if JUCE_IOS
    String defaultSans  { "Helvetica" },
           defaultSerif { "Times New Roman" },
           defaultFixed { "Courier New" };
   #else
    String defaultSans  { "Lucida Grande" },
           defaultSerif { "Times New Roman" },
           defaultFixed { "Menlo" };
   #endif
};

Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font)
{
    static DefaultFontNames defaultNames;

    auto newFont = font;
    auto& faceName = font.getTypefaceName();

    if (faceName == getDefaultSansSerifFontName())       newFont.setTypefaceName (defaultNames.defaultSans);
    else if (faceName == getDefaultSerifFontName())      newFont.setTypefaceName (defaultNames.defaultSerif);
    else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed);

    if (font.getTypefaceStyle() == getDefaultStyle())
        newFont.setTypefaceStyle ("Regular");

    return Typeface::createSystemTypefaceFor (newFont);
}

static bool canAllTypefacesBeUsedInLayout (const AttributedString& text)
{
    auto numCharacterAttributes = text.getNumAttributes();

    for (int i = 0; i < numCharacterAttributes; ++i)
    {
        if (auto tf = dynamic_cast<OSXTypeface*> (text.getAttribute(i).font.getTypeface()))
            if (tf->canBeUsedForLayout)
                continue;

        return false;
    }

    return true;
}

bool TextLayout::createNativeLayout (const AttributedString& text)
{
    if (canAllTypefacesBeUsedInLayout (text))
    {
        CoreTextTypeLayout::createLayout (*this, text);
        return true;
    }

    return false;
}

} // namespace juce
