// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser

#include "qfontengine_coretext_p.h"

#include <qpa/qplatformfontdatabase.h>
#include <QtCore/qendian.h>
#if QT_CONFIG(settings)
#include <QtCore/qsettings.h>
#endif
#include <QtCore/qoperatingsystemversion.h>
#include <QtGui/qpainterpath.h>
#include <private/qcoregraphics_p.h>
#include <private/qimage_p.h>
#include <private/qguiapplication_p.h>
#include <private/qstringiterator_p.h>
#include <qpa/qplatformtheme.h>

#include <cmath>

#if defined(Q_OS_MACOS)
#import <AppKit/AppKit.h>
#endif

#if defined(QT_PLATFORM_UIKIT)
#import <UIKit/UIKit.h>
#endif

#include <Accelerate/Accelerate.h>

// These are available cross platform, exported as kCTFontWeightXXX from CoreText.framework,
// but they are not documented and are not in public headers so are private API and exposed
// only through the NSFontWeightXXX and UIFontWeightXXX aliases in AppKit and UIKit (rdar://26109857)
#if defined(Q_OS_MACOS)
#define kCTFontWeightUltraLight NSFontWeightUltraLight
#define kCTFontWeightThin NSFontWeightThin
#define kCTFontWeightLight NSFontWeightLight
#define kCTFontWeightRegular NSFontWeightRegular
#define kCTFontWeightMedium NSFontWeightMedium
#define kCTFontWeightSemibold NSFontWeightSemibold
#define kCTFontWeightBold NSFontWeightBold
#define kCTFontWeightHeavy NSFontWeightHeavy
#define kCTFontWeightBlack NSFontWeightBlack
#elif defined(QT_PLATFORM_UIKIT)
#define kCTFontWeightUltraLight UIFontWeightUltraLight
#define kCTFontWeightThin UIFontWeightThin
#define kCTFontWeightLight UIFontWeightLight
#define kCTFontWeightRegular UIFontWeightRegular
#define kCTFontWeightMedium UIFontWeightMedium
#define kCTFontWeightSemibold UIFontWeightSemibold
#define kCTFontWeightBold UIFontWeightBold
#define kCTFontWeightHeavy UIFontWeightHeavy
#define kCTFontWeightBlack UIFontWeightBlack
#endif

QT_BEGIN_NAMESPACE

static float SYNTHETIC_ITALIC_SKEW = std::tan(14.f * std::acos(0.f) / 90.f);

bool QCoreTextFontEngine::ct_getSfntTable(void *user_data, uint tag, uchar *buffer, uint *length)
{
    CTFontRef ctfont = *(CTFontRef *)user_data;

    QCFType<CFDataRef> table = CTFontCopyTable(ctfont, tag, 0);
    if (!table)
        return false;

    CFIndex tableLength = CFDataGetLength(table);
    if (buffer && int(*length) >= tableLength)
        CFDataGetBytes(table, CFRangeMake(0, tableLength), buffer);
    *length = tableLength;
    Q_ASSERT(int(*length) > 0);
    return true;
}

QFont::Weight QCoreTextFontEngine::qtWeightFromCFWeight(float value)
{
#define COMPARE_WEIGHT_DISTANCE(ct_weight, qt_weight) \
    { \
        float d; \
        if ((d = qAbs(value - ct_weight)) < distance) { \
            distance = d; \
            ret = qt_weight; \
        } \
    }

    float distance = qAbs(value - kCTFontWeightBlack);
    QFont::Weight ret = QFont::Black;

    // Compare distance to system weight to find the closest match.
    // (Note: Must go from high to low, so that midpoints are rounded up)
    COMPARE_WEIGHT_DISTANCE(kCTFontWeightHeavy, QFont::ExtraBold);
    COMPARE_WEIGHT_DISTANCE(kCTFontWeightBold, QFont::Bold);
    COMPARE_WEIGHT_DISTANCE(kCTFontWeightSemibold, QFont::DemiBold);
    COMPARE_WEIGHT_DISTANCE(kCTFontWeightMedium, QFont::Medium);
    COMPARE_WEIGHT_DISTANCE(kCTFontWeightRegular, QFont::Normal);
    COMPARE_WEIGHT_DISTANCE(kCTFontWeightLight, QFont::Light);
    COMPARE_WEIGHT_DISTANCE(kCTFontWeightThin, QFont::ExtraLight);
    COMPARE_WEIGHT_DISTANCE(kCTFontWeightUltraLight, QFont::Thin);

#undef COMPARE_WEIGHT_DISTANCE

    return ret;
}

CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef)
{
    CGAffineTransform transform = CGAffineTransformIdentity;
    if (fontDef.stretch && fontDef.stretch != 100)
        transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1);
    return transform;
}

// Keeps font data alive until engine is disposed
class QCoreTextRawFontEngine : public QCoreTextFontEngine
{
public:
    QCoreTextRawFontEngine(CGFontRef font, const QFontDef &def, const QByteArray &fontData)
        : QCoreTextFontEngine(font, def)
        , m_fontData(fontData)
    {}
    QFontEngine *cloneWithSize(qreal pixelSize) const
    {
        QFontDef newFontDef = fontDef;
        newFontDef.pixelSize = pixelSize;
        newFontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi();

        return new QCoreTextRawFontEngine(cgFont, newFontDef, m_fontData);
    }
    QByteArray m_fontData;
};

QCoreTextFontEngine *QCoreTextFontEngine::create(const QByteArray &fontData,
                                                 qreal pixelSize,
                                                 QFont::HintingPreference hintingPreference,
                                                 const QMap<QFont::Tag, float> &variableAxisValues)
{
    Q_UNUSED(hintingPreference);
    Q_UNUSED(variableAxisValues);

    QCFType<CFDataRef> fontDataReference = fontData.toRawCFData();
    QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithCFData(fontDataReference);

    // Note: CTFontCreateWithGraphicsFont (which we call from the  QCoreTextFontEngine
    // constructor) has a bug causing it to retain the CGFontRef but never release it.
    // The result is that we are leaking the CGFont, CGDataProvider, and CGData, but
    // as the CGData is created from the raw QByteArray data, which we deref in the
    // subclass above during destruction, we're at least not leaking the font data,
    // (unless CoreText copies it internally). http://stackoverflow.com/questions/40805382/
    QCFType<CGFontRef> cgFont = CGFontCreateWithDataProvider(dataProvider);

    if (!cgFont) {
        qWarning("QCoreTextFontEngine::create: CGFontCreateWithDataProvider failed");
        return nullptr;
    }

    QFontDef def;
    def.pixelSize = pixelSize;
    def.pointSize = pixelSize * 72.0 / qt_defaultDpi();
    return new QCoreTextRawFontEngine(cgFont, def, fontData);
}

QCoreTextFontEngine::QCoreTextFontEngine(CTFontRef font, const QFontDef &def)
    : QCoreTextFontEngine(def)
{
    ctfont = QCFType<CTFontRef>::constructFromGet(font);
    cgFont = CTFontCopyGraphicsFont(font, nullptr);
    init();
}

QCoreTextFontEngine::QCoreTextFontEngine(CGFontRef font, const QFontDef &def)
    : QCoreTextFontEngine(def)
{
    cgFont = QCFType<CGFontRef>::constructFromGet(font);
    ctfont = CTFontCreateWithGraphicsFont(font, fontDef.pixelSize, &transform, nullptr);
    init();
}

QCoreTextFontEngine::QCoreTextFontEngine(const QFontDef &def)
    : QFontEngine(Mac)
{
    fontDef = def;
    transform = qt_transform_from_fontdef(fontDef);
}

QCoreTextFontEngine::~QCoreTextFontEngine()
{
}

void QCoreTextFontEngine::init()
{
    Q_ASSERT(ctfont);
    Q_ASSERT(cgFont);

    face_id.index = 0;
    QCFString name = CTFontCopyName(ctfont, kCTFontUniqueNameKey);
    face_id.filename = QString::fromCFString(name).toUtf8();
    face_id.variableAxes = fontDef.variableAxisValues;

    QCFString family = CTFontCopyFamilyName(ctfont);
    fontDef.families = QStringList(family);

    QCFString styleName = (CFStringRef) CTFontCopyAttribute(ctfont, kCTFontStyleNameAttribute);
    fontDef.styleName = styleName;

    synthesisFlags = 0;
    CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctfont);

    if (traits & kCTFontColorGlyphsTrait)
        glyphFormat = QFontEngine::Format_ARGB;
    else if (shouldSmoothFont() && fontSmoothing() == FontSmoothing::Subpixel)
        glyphFormat = QFontEngine::Format_A32;
    else
        glyphFormat = QFontEngine::Format_A8;

    if (traits & kCTFontItalicTrait)
        fontDef.style = QFont::StyleItalic;

    static const auto getTraitValue = [](CFDictionaryRef allTraits, CFStringRef trait) -> float {
        if (CFDictionaryContainsKey(allTraits, trait)) {
            CFNumberRef traitNum = (CFNumberRef) CFDictionaryGetValue(allTraits, trait);
            float v = 0;
            CFNumberGetValue(traitNum, kCFNumberFloatType, &v);
            return v;
        }
        return 0;
    };

    QCFType<CFDictionaryRef> allTraits = CTFontCopyTraits(ctfont);
    int slant = static_cast<int>(getTraitValue(allTraits, kCTFontSlantTrait) * 500 + 500);
    if (slant > 500 && !(traits & kCTFontItalicTrait))
        fontDef.style = QFont::StyleOblique;

    if (fontDef.weight >= QFont::Bold && !(traits & kCTFontBoldTrait) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD"))
        synthesisFlags |= SynthesizedBold;
    else
        fontDef.weight = QCoreTextFontEngine::qtWeightFromCFWeight(getTraitValue(allTraits, kCTFontWeightTrait));

    if (fontDef.style != QFont::StyleNormal && !(traits & kCTFontItalicTrait) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_ITALIC"))
        synthesisFlags |= SynthesizedItalic;

    avgCharWidth = 0;
    QByteArray os2Table = getSfntTable(QFont::Tag("OS/2").value());
    unsigned emSize = CTFontGetUnitsPerEm(ctfont);
    if (os2Table.size() >= 10) {
        fsType = qFromBigEndian<quint16>(os2Table.constData() + 8);
        // qAbs is a workaround for weird fonts like Lucida Grande
        qint16 width = qAbs(qFromBigEndian<qint16>(os2Table.constData() + 2));
        avgCharWidth = QFixed::fromReal(width * fontDef.pixelSize / emSize);
    } else
        avgCharWidth = QFontEngine::averageCharWidth();

    underlineThickness = QFixed::fromReal(CTFontGetUnderlineThickness(ctfont));
    underlinePos = -QFixed::fromReal(CTFontGetUnderlinePosition(ctfont));

    cache_cost = (CTFontGetAscent(ctfont) + CTFontGetDescent(ctfont)) * avgCharWidth.toInt() * 2000;

    kerningPairsLoaded = false;

    if (QCFType<CFArrayRef> variationAxes = CTFontCopyVariationAxes(ctfont)) {
        CFIndex count = CFArrayGetCount(variationAxes);
        for (CFIndex i = 0; i < count; ++i) {
            CFDictionaryRef variationAxis = CFDictionaryRef(CFArrayGetValueAtIndex(variationAxes, i));

            QFontVariableAxis fontVariableAxis;
            if (CFNumberRef tagRef = (CFNumberRef) CFDictionaryGetValue(variationAxis,
                                                                        kCTFontVariationAxisIdentifierKey)) {
                quint32 tag;
                CFNumberGetValue(tagRef, kCFNumberIntType, &tag);
                if (auto maybeTag = QFont::Tag::fromValue(tag))
                    fontVariableAxis.setTag(*maybeTag);
            }

            if (CFNumberRef minimumValueRef = (CFNumberRef) CFDictionaryGetValue(variationAxis,
                                                                                 kCTFontVariationAxisMinimumValueKey)) {
                float minimumValue;
                CFNumberGetValue(minimumValueRef, kCFNumberFloatType, &minimumValue);
                fontVariableAxis.setMinimumValue(minimumValue);
            }

            if (CFNumberRef maximumValueRef = (CFNumberRef) CFDictionaryGetValue(variationAxis,
                                                                                 kCTFontVariationAxisMaximumValueKey)) {
                float maximumValue;
                CFNumberGetValue(maximumValueRef, kCFNumberFloatType, &maximumValue);
                fontVariableAxis.setMaximumValue(maximumValue);
            }

            if (CFNumberRef defaultValueRef = (CFNumberRef) CFDictionaryGetValue(variationAxis,
                                                                                 kCTFontVariationAxisDefaultValueKey)) {
                float defaultValue;
                CFNumberGetValue(defaultValueRef, kCFNumberFloatType, &defaultValue);
                fontVariableAxis.setDefaultValue(defaultValue);
            }

            if (CFStringRef nameRef = (CFStringRef) CFDictionaryGetValue(variationAxis,
                                                                         kCTFontVariationAxisNameKey)) {
                fontVariableAxis.setName(QString::fromCFString(nameRef));
            }

            variableAxisList.append(fontVariableAxis);
        }
    }
}

int QCoreTextFontEngine::glyphCount() const
{
    return CTFontGetGlyphCount(ctfont);
}

glyph_t QCoreTextFontEngine::glyphIndex(uint ucs4) const
{
    int len = 0;

    QChar str[2];
    if (Q_UNLIKELY(QChar::requiresSurrogates(ucs4))) {
        str[len++] = QChar(QChar::highSurrogate(ucs4));
        str[len++] = QChar(QChar::lowSurrogate(ucs4));
    } else {
        str[len++] = QChar(ucs4);
    }

    CGGlyph glyphIndices[2];

    CTFontGetGlyphsForCharacters(ctfont, (const UniChar *)str, glyphIndices, len);

    return glyphIndices[0];
}

QString QCoreTextFontEngine::glyphName(glyph_t index) const
{
    QString result = QCFString(CTFontCopyNameForGlyph(ctfont, index));
    if (result.isEmpty())
        result = QFontEngine::glyphName(index);
    return result;
}

glyph_t QCoreTextFontEngine::findGlyph(QLatin1StringView name) const
{
    const QCFString cfName = CFStringCreateWithBytes(kCFAllocatorDefault,
                                                     reinterpret_cast<const UInt8 *>(name.data()),
                                                     name.size(), kCFStringEncodingASCII, false);
    const glyph_t result = CTFontGetGlyphWithName(ctfont, cfName);

    return result ? result : QFontEngine::findGlyph(name);
}

int QCoreTextFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs,
                                      int *nglyphs, QFontEngine::ShaperFlags flags) const
{
    Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
    if (*nglyphs < len) {
        *nglyphs = len;
        return -1;
    }

    QVarLengthArray<CGGlyph> cgGlyphs(len);
    CTFontGetGlyphsForCharacters(ctfont, (const UniChar*)str, cgGlyphs.data(), len);

    int glyph_pos = 0;
    int mappedGlyphs = 0;
    QStringIterator it(str, str + len);
    while (it.hasNext()) {
      qsizetype idx = it.index();
      char32_t ucs4 = it.next();
      glyphs->glyphs[glyph_pos] = cgGlyphs[idx];
      if (glyph_pos < idx)
          cgGlyphs[glyph_pos] = cgGlyphs[idx];
      if (glyphs->glyphs[glyph_pos] != 0 || isIgnorableChar(ucs4))
          mappedGlyphs++;
      glyph_pos++;
    }

    *nglyphs = glyph_pos;
    glyphs->numGlyphs = glyph_pos;

    if (!(flags & GlyphIndicesOnly))
        loadAdvancesForGlyphs(cgGlyphs, glyphs);

    return mappedGlyphs;
}

glyph_metrics_t QCoreTextFontEngine::boundingBox(glyph_t glyph)
{
    glyph_metrics_t ret;
    CGGlyph g = glyph;
    CGRect rect = CTFontGetBoundingRectsForGlyphs(ctfont, kCTFontOrientationHorizontal, &g, 0, 1);
    if (synthesisFlags & QFontEngine::SynthesizedItalic) {
        rect.size.width += rect.size.height * SYNTHETIC_ITALIC_SKEW;
    }
    ret.width = QFixed::fromReal(rect.size.width);
    ret.height = QFixed::fromReal(rect.size.height);
    ret.x = QFixed::fromReal(rect.origin.x);
    ret.y = -QFixed::fromReal(rect.origin.y) - ret.height;
    CGSize advances[1];
    CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationHorizontal, &g, advances, 1);
    ret.xoff = QFixed::fromReal(advances[0].width);
    ret.yoff = QFixed::fromReal(advances[0].height);

    return ret;
}

void QCoreTextFontEngine::initializeHeightMetrics() const
{
    m_ascent = QFixed::fromReal(CTFontGetAscent(ctfont));
    m_descent = QFixed::fromReal(CTFontGetDescent(ctfont));
    m_leading = QFixed::fromReal(CTFontGetLeading(ctfont));

    if (preferTypoLineMetrics())
        QFontEngine::initializeHeightMetrics();
    else
        m_heightMetricsQueried = true;
}

QFixed QCoreTextFontEngine::capHeight() const
{
    QFixed c = QFixed::fromReal(CTFontGetCapHeight(ctfont));
    if (c <= 0)
        return calculatedCapHeight();

    return c;
}

QFixed QCoreTextFontEngine::xHeight() const
{
    return QFixed::fromReal(CTFontGetXHeight(ctfont));
}

QFixed QCoreTextFontEngine::averageCharWidth() const
{
    return avgCharWidth;
}

qreal QCoreTextFontEngine::maxCharWidth() const
{
    // ### FIXME: 'W' might not be the widest character, but this is better than nothing
    const glyph_t glyph = glyphIndex('W');
    glyph_metrics_t bb = const_cast<QCoreTextFontEngine *>(this)->boundingBox(glyph);
    return bb.xoff.toReal();
}

bool QCoreTextFontEngine::hasColorGlyphs() const
{
    return glyphFormat == QFontEngine::Format_ARGB;
}

Q_GUI_EXPORT extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); // qtransform.cpp
void QCoreTextFontEngine::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight)
{
    QVarLengthArray<QFixedPoint> positions;
    QVarLengthArray<glyph_t> glyphs;
    QTransform matrix;
    matrix.translate(x, y);
    getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
    if (glyphs.size() == 0)
        return;

    CGContextSetFontSize(ctx, fontDef.pixelSize);

    CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx);

    CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight);

    // FIXME: Should we include the old matrix here? If so we need to assign it.
    Q_UNUSED(CGAffineTransformConcat(cgMatrix, oldTextMatrix));

    if (synthesisFlags & QFontEngine::SynthesizedItalic)
        cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0));

    cgMatrix = CGAffineTransformConcat(cgMatrix, transform);

    CGContextSetTextMatrix(ctx, cgMatrix);

    CGContextSetTextDrawingMode(ctx, kCGTextFill);

    QVarLengthArray<CGPoint> cgPositions(glyphs.size());
    QVarLengthArray<CGGlyph> cgGlyphs(glyphs.size());
    const qreal firstX = positions[0].x.toReal();
    const qreal firstY = positions[0].y.toReal();
    for (int i = 0; i < glyphs.size(); ++i) {
        cgPositions[i].x = positions[i].x.toReal() - firstX;
        cgPositions[i].y = firstY - positions[i].y.toReal();
        cgGlyphs[i] = glyphs[i];
    }

    //NSLog(@"Font inDraw %@  ctfont %@", CGFontCopyFullName(cgFont), CTFontCopyFamilyName(ctfont));

    CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal());
    CTFontDrawGlyphs(ctfont, cgGlyphs.data(), cgPositions.data(), glyphs.size(), ctx);

    if (synthesisFlags & QFontEngine::SynthesizedBold) {
        QTransform matrix(cgMatrix.a, cgMatrix.b, cgMatrix.c, cgMatrix.d, cgMatrix.tx, cgMatrix.ty);

        qreal boldOffset = 0.5 * lineThickness().toReal();
        qreal scale;
        qt_scaleForTransform(matrix, &scale);
        boldOffset *= scale;

        CGContextSetTextPosition(ctx,
                                 positions[0].x.toReal() + boldOffset,
                                 positions[0].y.toReal());
        CTFontDrawGlyphs(ctfont, cgGlyphs.data(), cgPositions.data(), glyphs.size(), ctx);
    }

    CGContextSetTextMatrix(ctx, oldTextMatrix);
}

struct ConvertPathInfo
{
    ConvertPathInfo(QPainterPath *newPath, const QPointF &newPos, qreal newStretch = 1.0) :
        path(newPath), pos(newPos), stretch(newStretch) {}
    QPainterPath *path;
    QPointF pos;
    qreal stretch;
};

static void convertCGPathToQPainterPath(void *info, const CGPathElement *element)
{
    ConvertPathInfo *myInfo = static_cast<ConvertPathInfo *>(info);
    switch(element->type) {
        case kCGPathElementMoveToPoint:
            myInfo->path->moveTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
                                 element->points[0].y + myInfo->pos.y());
            break;
        case kCGPathElementAddLineToPoint:
            myInfo->path->lineTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
                                 element->points[0].y + myInfo->pos.y());
            break;
        case kCGPathElementAddQuadCurveToPoint:
            myInfo->path->quadTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
                                 element->points[0].y + myInfo->pos.y(),
                                 (element->points[1].x * myInfo->stretch) + myInfo->pos.x(),
                                 element->points[1].y + myInfo->pos.y());
            break;
        case kCGPathElementAddCurveToPoint:
            myInfo->path->cubicTo((element->points[0].x * myInfo->stretch) + myInfo->pos.x(),
                                  element->points[0].y + myInfo->pos.y(),
                                  (element->points[1].x * myInfo->stretch) + myInfo->pos.x(),
                                  element->points[1].y + myInfo->pos.y(),
                                  (element->points[2].x * myInfo->stretch) + myInfo->pos.x(),
                                  element->points[2].y + myInfo->pos.y());
            break;
        case kCGPathElementCloseSubpath:
            myInfo->path->closeSubpath();
            break;
        default:
            qCWarning(lcQpaFonts) << "Unhandled path transform type: " << element->type;
    }

}

void QCoreTextFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nGlyphs,
                                          QPainterPath *path, QTextItem::RenderFlags)
{
    if (hasColorGlyphs())
        return; // We can't convert color-glyphs to path

    CGAffineTransform cgMatrix = CGAffineTransformIdentity;
    cgMatrix = CGAffineTransformScale(cgMatrix, 1, -1);

    if (synthesisFlags & QFontEngine::SynthesizedItalic)
        cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0));

    qreal stretch = fontDef.stretch ? qreal(fontDef.stretch) / 100 : 1.0;
    for (int i = 0; i < nGlyphs; ++i) {
        QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, glyphs[i], &cgMatrix);
        ConvertPathInfo info(path, positions[i].toPointF(), stretch);
        CGPathApply(cgpath, &info, convertCGPathToQPainterPath);
    }
}

static void qcoretextfontengine_scaleMetrics(glyph_metrics_t &br, const QTransform &matrix)
{
    if (matrix.isScaling()) {
        qreal hscale = matrix.m11();
        qreal vscale = matrix.m22();
        br.width  = QFixed::fromReal(br.width.toReal() * hscale);
        br.height = QFixed::fromReal(br.height.toReal() * vscale);
        br.x      = QFixed::fromReal(br.x.toReal() * hscale);
        br.y      = QFixed::fromReal(br.y.toReal() * vscale);
    }
}

glyph_metrics_t QCoreTextFontEngine::alphaMapBoundingBox(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &matrix, GlyphFormat format)
{
    if (matrix.type() > QTransform::TxScale)
        return QFontEngine::alphaMapBoundingBox(glyph, subPixelPosition, matrix, format);

    glyph_metrics_t br = boundingBox(glyph);

    QTransform xform = matrix;
    if (fontDef.stretch != 100 && fontDef.stretch != QFont::AnyStretch)
        xform.scale(fontDef.stretch / 100.0, 1.0);
    qcoretextfontengine_scaleMetrics(br, xform);

    // Normalize width and height
    if (br.width < 0)
        br.width = -br.width;
    if (br.height < 0)
        br.height = -br.height;

    if (format == QFontEngine::Format_A8 || format == QFontEngine::Format_A32 || format == QFontEngine::Format_ARGB) {
        // Drawing a vector based glyph with anti-aliasing enabled, or a
        // bitmap based glyph with pre-baked anti-aliasing, at x = 0,
        // will potentially fill the pixel to the left of 0, as the
        // coordinates are not aligned to the center of pixels. To
        // prevent clipping of this pixel we need to shift the glyph
        // in the bitmap one pixel to the right. The shift needs to
        // be reflected in the glyph metrics as well, so that the final
        // position of the glyph is correct, which is why doing the
        // shift in imageForGlyph() is not enough.
        br.x -= 1;

        // As we've shifted the glyph one pixel to the right, we need
        // to expand the width of the alpha map bounding box as well.
        br.width += 1;

        // But we have the same anti-aliasing problem on the right
        // hand side of the glyph, eg. if the width of the glyph
        // results in the bounding rect landing between two pixels.
        // We pad the bounding rect again to account for the possible
        // anti-aliased drawing.
        br.width += 1;

        // We also shift the glyph to right right based on the subpixel
        // position, so we pad the bounding box to take account for the
        // subpixel positions that may result in the glyph being drawn
        // one pixel to the right of the 0-subpixel position.
        br.width += 1;

        // The same same logic as for the x-position needs to be applied
        // to the y-position, except we don't need to compensate for
        // the subpixel positioning.
        br.y -= 1;
        br.height += 2;
    }

    return br;
}

/*
    Apple has gone through many iterations of its font smoothing algorithms,
    and there are many ways to enable or disable certain aspects of it. As
    keeping up with all the different toggles and behavior differences between
    macOS versions is tricky, we resort to rendering a single glyph in a few
    configurations, picking up the font smoothing algorithm from the observed
    result.

    The possible values are:

     - Disabled: No font smoothing is applied.

       Possibly triggered by the user unchecking the "Use font smoothing when
       available" checkbox in the system preferences or setting AppleFontSmoothing
       to 0. Also controlled by the CGContextSetAllowsFontSmoothing() API,
       which gets its default from the settings above. This API overrides
       the more granular CGContextSetShouldSmoothFonts(), which we use to
       enable (request) or disable font smoothing.

       Note that this does not exclude normal antialiasing, controlled by
       the CGContextSetShouldAntialias() API.

     - Subpixel: Font smoothing is applied, and affects subpixels.

       This was the default mode on macOS versions prior to 10.14 (Mojave).
       The font dilation (stem darkening) parameters were controlled by the
       AppleFontSmoothing setting, ranging from 1 to 3 (light to strong).

       On Mojave it is no longer supported, but can be triggered by a legacy
       override (CGFontRenderingFontSmoothingDisabled=NO), so we need to
       still account for it, otherwise users will have a bad time.

     - Grayscale: Font smoothing is applied, but does not affect subpixels.

       This is the default mode on macOS 10.14 (Mojave). The font dilation
       (stem darkening) parameters are not affected by the AppleFontSmoothing
       setting, but are instead computed based on the fill color used when
       drawing the glyphs (white fill gives a lighter dilation than black
       fill). This affects how we build our glyph cache, since we produce
       alpha maps by drawing white on black.
*/
QCoreTextFontEngine::FontSmoothing QCoreTextFontEngine::fontSmoothing()
{
    static const FontSmoothing cachedFontSmoothing = [] {
        static const int kSize = 10;
        QCFType<CTFontRef> font = CTFontCreateWithName(CFSTR("Helvetica"), kSize, nullptr);

        UniChar character('X'); CGGlyph glyph;
        CTFontGetGlyphsForCharacters(font, &character, &glyph, 1);

        auto drawGlyph = [&](bool smooth) -> QImage {
            QImage image(kSize, kSize, QImage::Format_RGB32);
            image.fill(0);

            QMacCGContext ctx(&image);
            CGContextSetTextDrawingMode(ctx, kCGTextFill);
            CGContextSetGrayFillColor(ctx, 1, 1);

            // Will be ignored if CGContextSetAllowsFontSmoothing() has been
            // set to false by CoreGraphics based on user defaults.
            CGContextSetShouldSmoothFonts(ctx, smooth);

            CTFontDrawGlyphs(font, &glyph, &CGPointZero, 1, ctx);
            return image;
        };

        QImage nonSmoothed = drawGlyph(false);
        QImage smoothed = drawGlyph(true);

        FontSmoothing fontSmoothing = FontSmoothing::Disabled;
        [&] {
            for (int x = 0; x < kSize; ++x) {
                for (int y = 0; y < kSize; ++y) {
                    QRgb sp = smoothed.pixel(x, y);
                    if (qRed(sp) != qGreen(sp) || qRed(sp) != qBlue(sp)) {
                        fontSmoothing = FontSmoothing::Subpixel;
                        return;
                    }

                    if (sp != nonSmoothed.pixel(x, y))
                        fontSmoothing = FontSmoothing::Grayscale;
                }
            }
        }();

        auto defaults = [NSUserDefaults standardUserDefaults];
        qCDebug(lcQpaFonts) << "Resolved font smoothing algorithm. Defaults ="
            << [[defaults dictionaryRepresentation] dictionaryWithValuesForKeys:@[
                @"AppleFontSmoothing",
                @"CGFontRenderingFontSmoothingDisabled"
            ]] << "Result =" << fontSmoothing;

        return fontSmoothing;
    }();

    return cachedFontSmoothing;
}

bool QCoreTextFontEngine::shouldAntialias() const
{
    return !(fontDef.styleStrategy & QFont::NoAntialias);
}

bool QCoreTextFontEngine::shouldSmoothFont() const
{
    if (hasColorGlyphs())
        return false;

    if (!shouldAntialias())
        return false;

    switch (fontSmoothing()) {
    case Disabled: return false;
    case Subpixel: return !(fontDef.styleStrategy & QFont::NoSubpixelAntialias);
    case Grayscale: return true;
    }

    Q_UNREACHABLE();
}

bool QCoreTextFontEngine::expectsGammaCorrectedBlending(QFontEngine::GlyphFormat format) const
{
    Q_UNUSED(format);
    return shouldSmoothFont() && fontSmoothing() == Subpixel;
}

qreal QCoreTextFontEngine::fontSmoothingGamma()
{
    return 2.0;
}

QImage QCoreTextFontEngine::imageForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &matrix, const QColor &color)
{
    glyph_metrics_t br = alphaMapBoundingBox(glyph, subPixelPosition, matrix, glyphFormat);

    QImage::Format imageFormat = hasColorGlyphs() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32;
    QImage im(br.width.ceil().toInt(), br.height.ceil().toInt(), imageFormat);
    if (!im.width() || !im.height())
        return im;

    auto cgImageFormat = qt_mac_cgImageFormatForImage(im);
    if (!cgImageFormat)
        return im;

    QCFType<CGContextRef> ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(),
        cgImageFormat->bitsPerComponent, im.bytesPerLine(), cgImageFormat->colorSpace,
        cgImageFormat->bitmapInfo);
    Q_ASSERT(ctx);

    CGContextSetShouldAntialias(ctx, shouldAntialias());

    const bool shouldSmooth = shouldSmoothFont();
    CGContextSetShouldSmoothFonts(ctx, shouldSmooth);

#if defined(Q_OS_MACOS)
    auto glyphColor = [&] {
        if (shouldSmooth && fontSmoothing() == Grayscale) {
            // The grayscale font smoothing algorithm introduced in macOS Mojave (10.14) adjusts
            // its dilation (stem darkening) parameters based on the fill color. This means our
            // default approach of drawing white on black to produce the alpha map will result
            // in non-native looking text when then drawn as black on white during the final blit.
            // As a workaround we use the application's current appearance to decide whether to
            // draw with white or black fill, and then invert the glyph image in the latter case,
            // producing an alpha map. This covers the most common use-cases, but longer term we
            // should propagate the fill color all the way from the paint engine, and include it
            // in the key for the glyph cache.

            if (auto *platformTheme = QGuiApplicationPrivate::platformTheme()) {
                if (platformTheme->colorScheme() != Qt::ColorScheme::Dark)
                    return kCGColorBlack;
            }
        }
        return kCGColorWhite;
    }();

    const bool blackOnWhiteGlyphs = glyphColor == kCGColorBlack;
    if (blackOnWhiteGlyphs)
        im.fill(Qt::white);
    else
#endif
        im.fill(0);

    CGContextSetFontSize(ctx, fontDef.pixelSize);

    CGAffineTransform cgMatrix = CGAffineTransformIdentity;

    if (synthesisFlags & QFontEngine::SynthesizedItalic)
        cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, SYNTHETIC_ITALIC_SKEW, 1, 0, 0));

    if (!hasColorGlyphs()) // CTFontDrawGlyphs incorporates the font's matrix already
        cgMatrix = CGAffineTransformConcat(cgMatrix, transform);

    if (matrix.isScaling())
        cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMakeScale(matrix.m11(), matrix.m22()));

    CGGlyph cgGlyph = glyph;

    qreal pos_x = -br.x.truncate() + subPixelPosition.x.toReal();
    qreal pos_y = im.height() + br.y.toReal() - subPixelPosition.y.toReal();

    if (!hasColorGlyphs()) {
        CGContextSetTextMatrix(ctx, cgMatrix);
#if defined(Q_OS_MACOS)
        CGContextSetFillColorWithColor(ctx, CGColorGetConstantColor(glyphColor));
#else
        CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
#endif
        CGContextSetTextDrawingMode(ctx, kCGTextFill);
        CGContextSetTextPosition(ctx, pos_x, pos_y);

        CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);

        if (synthesisFlags & QFontEngine::SynthesizedBold) {
            qreal boldOffset = 0.5 * lineThickness().toReal();

            qreal scale;
            qt_scaleForTransform(matrix, &scale);
            boldOffset *= scale;

            CGContextSetTextPosition(ctx, pos_x + boldOffset, pos_y);
            CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
        }
    } else {
        CGContextSetRGBFillColor(ctx, color.redF(), color.greenF(), color.blueF(), color.alphaF());

        // CGContextSetTextMatrix does not work with color glyphs, so we use
        // the CTM instead. This means we must translate the CTM as well, to
        // set the glyph position, instead of using CGContextSetTextPosition.
        CGContextTranslateCTM(ctx, pos_x, pos_y);
        CGContextConcatCTM(ctx, cgMatrix);

        // CGContextShowGlyphsWithAdvances does not support the 'sbix' color-bitmap
        // glyphs in the Apple Color Emoji font, so we use CTFontDrawGlyphs instead.
        CTFontDrawGlyphs(ctfont, &cgGlyph, &CGPointZero, 1, ctx);
    }

    if (expectsGammaCorrectedBlending())
        qGamma_correct_back_to_linear_cs(&im);

#if defined(Q_OS_MACOS)
    if (blackOnWhiteGlyphs)
        im.invertPixels();
#endif

    return im;
}

QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition)
{
    return alphaMapForGlyph(glyph, subPixelPosition, QTransform());
}

QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &x)
{
    if (x.type() > QTransform::TxScale)
        return QFontEngine::alphaMapForGlyph(glyph, subPixelPosition, x);

    QImage im = imageForGlyph(glyph, subPixelPosition, x);

    QImage alphaMap(im.width(), im.height(), QImage::Format_Alpha8);

    for (int y=0; y<im.height(); ++y) {
        uint *src = (uint*) im.scanLine(y);
        uchar *dst = alphaMap.scanLine(y);
        for (int x=0; x<im.width(); ++x) {
            *dst = qGray(*src);
            ++dst;
            ++src;
        }
    }

    return alphaMap;
}

QImage QCoreTextFontEngine::alphaRGBMapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &x)
{
    if (x.type() > QTransform::TxScale)
        return QFontEngine::alphaRGBMapForGlyph(glyph, subPixelPosition, x);

    return imageForGlyph(glyph, subPixelPosition, x);
}

QImage QCoreTextFontEngine::bitmapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &t, const QColor &color)
{
    if (t.type() > QTransform::TxScale)
        return QFontEngine::bitmapForGlyph(glyph, subPixelPosition, t, color);

    return imageForGlyph(glyph, subPixelPosition, t, color);
}

void QCoreTextFontEngine::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags flags) const
{
    Q_UNUSED(flags);

    const int numGlyphs = glyphs->numGlyphs;
    QVarLengthArray<CGGlyph> cgGlyphs(numGlyphs);

    for (int i = 0; i < numGlyphs; ++i) {
        Q_ASSERT(!QFontEngineMulti::highByte(glyphs->glyphs[i]));
        cgGlyphs[i] = glyphs->glyphs[i];
    }

    loadAdvancesForGlyphs(cgGlyphs, glyphs);
}

void QCoreTextFontEngine::loadAdvancesForGlyphs(QVarLengthArray<CGGlyph> &cgGlyphs, QGlyphLayout *glyphs) const
{
    const int numGlyphs = glyphs->numGlyphs;
    QVarLengthArray<CGSize> advances(numGlyphs);
    CTFontGetAdvancesForGlyphs(ctfont, kCTFontOrientationHorizontal, cgGlyphs.data(), advances.data(), numGlyphs);

    qreal stretch = fontDef.stretch != QFont::AnyStretch ? fontDef.stretch / 100.0 : 1.0;
    for (int i = 0; i < numGlyphs; ++i)
        glyphs->advances[i] = QFixed::fromReal(advances[i].width * stretch);
}

QFontEngine::FaceId QCoreTextFontEngine::faceId() const
{
    return face_id;
}

bool QCoreTextFontEngine::canRender(const QChar *string, int len) const
{
    QVarLengthArray<CGGlyph> cgGlyphs(len);
    return CTFontGetGlyphsForCharacters(ctfont, (const UniChar *) string, cgGlyphs.data(), len);
}

bool QCoreTextFontEngine::getSfntTableData(uint tag, uchar *buffer, uint *length) const
{
    return ct_getSfntTable((void *)&ctfont, tag, buffer, length);
}

void QCoreTextFontEngine::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metric)
{
    CGAffineTransform cgMatrix = CGAffineTransformIdentity;

    qreal emSquare = CTFontGetUnitsPerEm(ctfont);
    qreal scale = emSquare / CTFontGetSize(ctfont);
    cgMatrix = CGAffineTransformScale(cgMatrix, scale, -scale);

    QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, (CGGlyph) glyph, &cgMatrix);
    ConvertPathInfo info(path, QPointF(0,0));
    CGPathApply(cgpath, &info, convertCGPathToQPainterPath);

    *metric = boundingBox(glyph);
    // scale the metrics too
    metric->width  = QFixed::fromReal(metric->width.toReal() * scale);
    metric->height = QFixed::fromReal(metric->height.toReal() * scale);
    metric->x      = QFixed::fromReal(metric->x.toReal() * scale);
    metric->y      = QFixed::fromReal(metric->y.toReal() * scale);
    metric->xoff   = QFixed::fromReal(metric->xoff.toReal() * scale);
    metric->yoff   = QFixed::fromReal(metric->yoff.toReal() * scale);
}

QFixed QCoreTextFontEngine::emSquareSize() const
{
    return QFixed(int(CTFontGetUnitsPerEm(ctfont)));
}

QFontEngine *QCoreTextFontEngine::cloneWithSize(qreal pixelSize) const
{
    QFontDef newFontDef = fontDef;
    newFontDef.pixelSize = pixelSize;
    newFontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi();

    return new QCoreTextFontEngine(cgFont, newFontDef);
}

Qt::HANDLE QCoreTextFontEngine::handle() const
{
    return (Qt::HANDLE)(static_cast<CTFontRef>(ctfont));
}

bool QCoreTextFontEngine::supportsTransformation(const QTransform &transform) const
{
    if (transform.type() < QTransform::TxScale)
        return true;
    else if (transform.type() == QTransform::TxScale &&
             transform.m11() >= 0 && transform.m22() >= 0)
        return true;
    else
        return false;
}

QFixed QCoreTextFontEngine::lineThickness() const
{
    return underlineThickness;
}

QFixed QCoreTextFontEngine::underlinePosition() const
{
    return underlinePos;
}

QFontEngine::Properties QCoreTextFontEngine::properties() const
{
    Properties result;

    QCFString psName, copyright;
    psName = CTFontCopyPostScriptName(ctfont);
    copyright = CTFontCopyName(ctfont, kCTFontCopyrightNameKey);
    result.postscriptName = QString::fromCFString(psName).toUtf8();
    result.copyright = QString::fromCFString(copyright).toUtf8();

    qreal emSquare = CTFontGetUnitsPerEm(ctfont);
    qreal scale = emSquare / CTFontGetSize(ctfont);

    CGRect cgRect = CTFontGetBoundingBox(ctfont);
    result.boundingBox = QRectF(cgRect.origin.x * scale,
                                -CTFontGetAscent(ctfont) * scale,
                                cgRect.size.width * scale,
                                cgRect.size.height * scale);

    result.emSquare = emSquareSize();
    result.ascent = QFixed::fromReal(CTFontGetAscent(ctfont) * scale);
    result.descent = QFixed::fromReal(CTFontGetDescent(ctfont) * scale);
    result.leading = QFixed::fromReal(CTFontGetLeading(ctfont) * scale);
    result.italicAngle = QFixed::fromReal(CTFontGetSlantAngle(ctfont));
    result.capHeight = QFixed::fromReal(CTFontGetCapHeight(ctfont) * scale);
    result.lineWidth = QFixed::fromReal(CTFontGetUnderlineThickness(ctfont) * scale);

    return result;
}

void QCoreTextFontEngine::doKerning(QGlyphLayout *g, ShaperFlags flags) const
{
    if (!kerningPairsLoaded) {
        kerningPairsLoaded = true;
        qreal emSquare = CTFontGetUnitsPerEm(ctfont);
        qreal scale = emSquare / CTFontGetSize(ctfont);

        const_cast<QCoreTextFontEngine *>(this)->loadKerningPairs(QFixed::fromReal(scale));
    }

    QFontEngine::doKerning(g, flags);
}

QList<QFontVariableAxis> QCoreTextFontEngine::variableAxes() const
{
    return variableAxisList;
}

QT_END_NAMESPACE
