// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <QTest>
#include <QBuffer>
#include <QtEndian>
#if QT_CONFIG(process)
#include <QProcess>
#endif

#include <qfont.h>
#include <private/qfont_p.h>
#include <qfontdatabase.h>
#include <qfontinfo.h>
#include <qstringlist.h>
#include <qguiapplication.h>
#ifndef QT_NO_WIDGETS
#include <qwidget.h>
#endif
#include <qlist.h>
#include <QtTest/private/qemulationdetector_p.h>
#include <private/qcomparisontesthelper_p.h>
#include <qpa/qplatformfontdatabase.h>
#include <qpa/qplatformintegration.h>
#include <QtGui/private/qguiapplication_p.h>

using namespace Qt::StringLiterals;

class tst_QFont : public QObject
{
Q_OBJECT

private slots:
    void initTestCase();
    void getSetCheck();
    void exactMatch();
    void compare();
    void resolve();
#ifndef QT_NO_WIDGETS
    void resetFont();
#endif
    void isCopyOf();
    void italicOblique();
    void insertAndRemoveSubstitutions();
    void serialize_data();
    void serialize();
    void deserializeQt515();

    void styleName();
    void defaultFamily_data();
    void defaultFamily();
    void toAndFromString();
    void fromStringCompatibility_data();
    void fromStringCompatibility();
    void fromStringWithoutStyleName();
    void fromStringWithoutFeaturesOrVariableAxes();
    void fromDegenerateString_data();
    void fromDegenerateString();

    void sharing();
    void familyNameWithCommaQuote_data();
    void familyNameWithCommaQuote();
    void setFamilies_data();
    void setFamilies();
    void setFamiliesAndFamily_data();
    void setFamiliesAndFamily();
    void featureAccessors();
    void tagCompares_data();
    void tagCompares();

    void variableAxes();

private:
    QString m_testFontVariable;
};

void tst_QFont::initTestCase()
{
    m_testFontVariable = QFINDTESTDATA("testfont_variable.ttf");
}

// Testing get/set functions
void tst_QFont::getSetCheck()
{
    QFont obj1;
    // Style QFont::style()
    // void QFont::setStyle(Style)
    obj1.setStyle(QFont::Style(QFont::StyleNormal));
    QCOMPARE(QFont::Style(QFont::StyleNormal), obj1.style());
    obj1.setStyle(QFont::Style(QFont::StyleItalic));
    QCOMPARE(QFont::Style(QFont::StyleItalic), obj1.style());
    obj1.setStyle(QFont::Style(QFont::StyleOblique));
    QCOMPARE(QFont::Style(QFont::StyleOblique), obj1.style());

    // StyleStrategy QFont::styleStrategy()
    // void QFont::setStyleStrategy(StyleStrategy)
    obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferDefault));
    QCOMPARE(QFont::StyleStrategy(QFont::PreferDefault), obj1.styleStrategy());
    obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferBitmap));
    QCOMPARE(QFont::StyleStrategy(QFont::PreferBitmap), obj1.styleStrategy());
    obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferDevice));
    QCOMPARE(QFont::StyleStrategy(QFont::PreferDevice), obj1.styleStrategy());
    obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferOutline));
    QCOMPARE(QFont::StyleStrategy(QFont::PreferOutline), obj1.styleStrategy());
    obj1.setStyleStrategy(QFont::StyleStrategy(QFont::ForceOutline));
    QCOMPARE(QFont::StyleStrategy(QFont::ForceOutline), obj1.styleStrategy());
    obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferMatch));
    QCOMPARE(QFont::StyleStrategy(QFont::PreferMatch), obj1.styleStrategy());
    obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferQuality));
    QCOMPARE(QFont::StyleStrategy(QFont::PreferQuality), obj1.styleStrategy());
    obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferAntialias));
    QCOMPARE(QFont::StyleStrategy(QFont::PreferAntialias), obj1.styleStrategy());
    obj1.setStyleStrategy(QFont::StyleStrategy(QFont::NoAntialias));
    QCOMPARE(QFont::StyleStrategy(QFont::NoAntialias), obj1.styleStrategy());
}

void tst_QFont::exactMatch()
{
    QFont font;

    // Check if a non-existing font hasn't an exact match
    font = QFont( "BogusFont", 33 );
    QVERIFY( !font.exactMatch() );
    QVERIFY(!QFont("sans").exactMatch());
    QVERIFY(!QFont("sans-serif").exactMatch());
    QVERIFY(!QFont("serif").exactMatch());
    QVERIFY(!QFont("monospace").exactMatch());

    font.setFamilies(QStringList() << "BogusFont");
    QVERIFY(!font.exactMatch());
    QVERIFY(!QFont("sans").exactMatch());
    QVERIFY(!QFont("sans-serif").exactMatch());
    QVERIFY(!QFont("serif").exactMatch());
    QVERIFY(!QFont("monospace").exactMatch());

    // Confirm that exactMatch is true for a valid font
    const QString family = QFontDatabase::families().first();
    const QString style = QFontDatabase::styles(family).first();
    const int pointSize = QFontDatabase::pointSizes(family, style).first();
    font = QFontDatabase::font(family, style, pointSize);
    QVERIFY(font.exactMatch());

    if (QFontDatabase::families().contains("Arial")) {
        font = QFont("Arial");
        QVERIFY(font.exactMatch());
        font = QFont(QString());
        font.setFamilies({"Arial"});
        QVERIFY(font.exactMatch());
    }
}

void tst_QFont::italicOblique()
{
    QStringList families = QFontDatabase::families();
    if (families.isEmpty())
        return;

    QStringList::ConstIterator f_it, f_end = families.end();
    for (f_it = families.begin(); f_it != f_end; ++f_it) {

        QString family = *f_it;
        QStringList styles = QFontDatabase::styles(family);
        QStringList::ConstIterator s_it, s_end = styles.end();
        for (s_it = styles.begin(); s_it != s_end; ++s_it) {
            QString style = *s_it;

            if (QFontDatabase::isSmoothlyScalable(family, style)) {
                QFont f = QFontDatabase::font(family, style, 12);
                bool wasItalic = f.italic();

                if (style.contains("Oblique")) {
                    style.replace("Oblique", "Italic");
                } else if (style.contains("Italic")) {
                    style.replace("Italic", "Oblique");
                } else {
                    continue;
                }
                f = QFontDatabase::font(family, style, 12);
                QVERIFY2(f.italic() == wasItalic, qPrintable(QString::asprintf("Failed for font \"%ls\"", qUtf16Printable(f.family()))));
            }
        }
    }
}

void tst_QFont::compare()
{
    QFont font;
    {
        QFont font2 = font;
        font2.setPointSize(24);
        QVERIFY(font != font2);
        QCOMPARE(font < font2,!(font2 < font));
    }
    {
        QFont font2 = font;
        font2.setPixelSize(24);
        QVERIFY(font != font2);
        QCOMPARE(font < font2,!(font2 < font));
    }

    font.setPointSize(12);
    font.setItalic(false);
    font.setWeight(QFont::Normal);
    font.setUnderline(false);
    font.setStrikeOut(false);
    font.setOverline(false);
    {
        QFont font2 = font;
        font2.setPointSize(24);
        QVERIFY(font != font2);
        QCOMPARE(font < font2,!(font2 < font));
    }
    {
        QFont font2 = font;
        font2.setPixelSize(24);
        QVERIFY(font != font2);
        QCOMPARE(font < font2,!(font2 < font));
    }
    {
        QFont font2 = font;

        font2.setItalic(true);
        QVERIFY(font != font2);
        QCOMPARE(font < font2,!(font2 < font));
        font2.setItalic(false);
        QCOMPARE(font, font2);
        QVERIFY(!(font < font2));

        font2.setWeight(QFont::Bold);
        QVERIFY(font != font2);
        QCOMPARE(font < font2,!(font2 < font));
        font2.setWeight(QFont::Normal);
        QCOMPARE(font, font2);
        QVERIFY(!(font < font2));

        font.setUnderline(true);
        QVERIFY(font != font2);
        QCOMPARE(font < font2,!(font2 < font));
        font.setUnderline(false);
        QCOMPARE(font, font2);
        QVERIFY(!(font < font2));

        font.setStrikeOut(true);
        QVERIFY(font != font2);
        QCOMPARE(font < font2,!(font2 < font));
        font.setStrikeOut(false);
        QCOMPARE(font, font2);
        QVERIFY(!(font < font2));

        font.setOverline(true);
        QVERIFY(font != font2);
        QCOMPARE(font < font2,!(font2 < font));
        font.setOverline(false);
        QCOMPARE(font, font2);
        QVERIFY(!(font < font2));

        font.setCapitalization(QFont::SmallCaps);
        QVERIFY(font != font2);
        QCOMPARE(font < font2,!(font2 < font));
        font.setCapitalization(QFont::MixedCase);
        QCOMPARE(font, font2);
        QVERIFY(!(font < font2));
    }
}

void tst_QFont::resolve()
{
    QFont font;
    font.setPointSize(font.pointSize() * 2);
    font.setItalic(false);
    font.setWeight(QFont::Normal);
    font.setUnderline(false);
    font.setStrikeOut(false);
    font.setOverline(false);
    font.setStretch(QFont::Unstretched);

    QFont font1;
    font1.setWeight(QFont::Bold);
    QFont font2 = font1.resolve(font);

    QCOMPARE(font2.weight(), font1.weight());

    QCOMPARE(font2.pointSize(), font.pointSize());
    QCOMPARE(font2.italic(), font.italic());
    QCOMPARE(font2.underline(), font.underline());
    QCOMPARE(font2.overline(), font.overline());
    QCOMPARE(font2.strikeOut(), font.strikeOut());
    QCOMPARE(font2.stretch(), font.stretch());

    QFont font3;
    font3.setStretch(QFont::UltraCondensed);
    QFont font4 = font3.resolve(font1).resolve(font);

    QCOMPARE(font4.stretch(), font3.stretch());

    QCOMPARE(font4.weight(), font.weight());
    QCOMPARE(font4.pointSize(), font.pointSize());
    QCOMPARE(font4.italic(), font.italic());
    QCOMPARE(font4.underline(), font.underline());
    QCOMPARE(font4.overline(), font.overline());
    QCOMPARE(font4.strikeOut(), font.strikeOut());


    QFont f1,f2,f3;
    f2.setPointSize(45);
    f3.setPointSize(55);

    QFont f4 = f1.resolve(f2);
    QCOMPARE(f4.pointSize(), 45);
    f4 = f4.resolve(f3);
    QCOMPARE(f4.pointSize(), 55);

    QFont font5, font6;
    const QStringList fontFamilies = { QStringLiteral("Arial") };
    font5.setFamilies(fontFamilies);
    font6 = font6.resolve(font5);
    QCOMPARE(font6.families(), fontFamilies);

    QFont font7, font8;
    // This will call setFamilies() directly now
    font7.setFamily(QLatin1String("Helvetica"));
    font8.setFamilies(fontFamilies);
    font7 = font7.resolve(font8);
    QCOMPARE(font7.families(), QStringList({"Helvetica"}));
    QCOMPARE(font7.family(), "Helvetica");
}

#ifndef QT_NO_WIDGETS
void tst_QFont::resetFont()
{
    QWidget parent;
    QWidget firstChild(&parent);
    QFont parentFont = parent.font();
    parentFont.setPointSize(parentFont.pointSize() + 2);
    parent.setFont(parentFont);

    QFont childFont = firstChild.font();
    childFont.setBold(!childFont.bold());
    firstChild.setFont(childFont);

    QWidget secondChild(&parent);
    secondChild.setFont(childFont);

    QVERIFY(parentFont.resolveMask() != 0);
    QVERIFY(childFont.resolveMask() != 0);
    QVERIFY(childFont != parentFont);

    // reset font on both children
    firstChild.setFont(QFont());
    secondChild.setFont(QFont());

    QCOMPARE(firstChild.font().resolveMask(), QFont::SizeResolved);
    QCOMPARE(secondChild.font().resolveMask(), QFont::SizeResolved);
    QCOMPARE(firstChild.font().pointSize(), parent.font().pointSize());
    QCOMPARE(secondChild.font().pointSize(), parent.font().pointSize());
    QVERIFY(parent.font().resolveMask() != 0);
}
#endif

void tst_QFont::isCopyOf()
{
    QFont font;
    QVERIFY(font.isCopyOf(QGuiApplication::font()));

    QFont font2("bogusfont",  23);
    QVERIFY(! font2.isCopyOf(QGuiApplication::font()));

    QFont font3 = font;
    QVERIFY(font3.isCopyOf(font));

    font3.setPointSize(256);
    QVERIFY(!font3.isCopyOf(font));
    font3.setPointSize(font.pointSize());
    QVERIFY(!font3.isCopyOf(font));
}

void tst_QFont::insertAndRemoveSubstitutions()
{
    QFont::removeSubstitutions("BogusFontFamily");
    // make sure it is empty before we start
    QVERIFY(QFont::substitutes("BogusFontFamily").isEmpty());
    QVERIFY(QFont::substitutes("bogusfontfamily").isEmpty());

    // inserting Foo
    QFont::insertSubstitution("BogusFontFamily", "Foo");
    QCOMPARE(QFont::substitutes("BogusFontFamily").size(), 1);
    QCOMPARE(QFont::substitutes("bogusfontfamily").size(), 1);

    // inserting Bar and Baz
    QStringList moreFonts;
    moreFonts << "Bar" << "Baz";
    QFont::insertSubstitutions("BogusFontFamily", moreFonts);
    QCOMPARE(QFont::substitutes("BogusFontFamily").size(), 3);
    QCOMPARE(QFont::substitutes("bogusfontfamily").size(), 3);

    QFont::removeSubstitutions("BogusFontFamily");
    // make sure it is empty again
    QVERIFY(QFont::substitutes("BogusFontFamily").isEmpty());
    QVERIFY(QFont::substitutes("bogusfontfamily").isEmpty());
}

Q_DECLARE_METATYPE(QDataStream::Version)

void tst_QFont::serialize_data()
{
    QTest::addColumn<QFont>("font");
    // The version in which the tested feature was added.
    QTest::addColumn<QDataStream::Version>("minimumStreamVersion");

    QFont basicFont;
    // Versions <= Qt 2.1 had broken point size serialization,
    // so we set an integer point size.
    basicFont.setPointSize(9);
    // Versions <= Qt 5.4 didn't serialize styleName, so clear it
    basicFont.setStyleName(QString());

    QFont font = basicFont;
    QTest::newRow("defaultConstructed") << font << QDataStream::Qt_1_0;

    font.setLetterSpacing(QFont::AbsoluteSpacing, 105);
    QTest::newRow("letterSpacing=105") << font << QDataStream::Qt_4_5;

    font = basicFont;
    font.setWordSpacing(50.0);
    QTest::newRow("wordSpacing") << font << QDataStream::Qt_4_5;

    font = basicFont;
    font.setPointSize(20);
    QTest::newRow("pointSize") << font << QDataStream::Qt_1_0;

    font = basicFont;
    font.setPixelSize(32);
    QTest::newRow("pixelSize") << font << QDataStream::Qt_3_0;

    font = basicFont;
    font.setStyleHint(QFont::Monospace);
    QTest::newRow("styleHint") << font << QDataStream::Qt_1_0;

    font = basicFont;
    font.setStretch(4000);
    QTest::newRow("stretch") << font << QDataStream::Qt_4_3;

    font = basicFont;
    font.setWeight(QFont::Light);
    QTest::newRow("weight") << font << QDataStream::Qt_1_0;

    font = basicFont;
    font.setUnderline(true);
    QTest::newRow("underline") << font << QDataStream::Qt_1_0;

    font = basicFont;
    font.setStrikeOut(true);
    QTest::newRow("strikeOut") << font << QDataStream::Qt_1_0;

    font = basicFont;
    font.setFixedPitch(true);
    // This fails for versions less than this, as ignorePitch is set to false
    // whenever setFixedPitch() is called, but ignorePitch is considered an
    // extended bit, which were apparently not available until 4.4.
    QTest::newRow("fixedPitch") << font << QDataStream::Qt_4_4;

    font = basicFont;
    font.setLetterSpacing(QFont::AbsoluteSpacing, 10);
    // Fails for 4.4 because letterSpacing wasn't read until 4.5.
    QTest::newRow("letterSpacing=10") << font << QDataStream::Qt_4_5;

    font = basicFont;
    font.setKerning(false);
    QTest::newRow("kerning") << font << QDataStream::Qt_4_0;

    font = basicFont;
    font.setStyleStrategy(QFont::NoFontMerging);
    // This wasn't read properly until 5.4.
    QTest::newRow("styleStrategy") << font << QDataStream::Qt_5_4;

    font = basicFont;
    font.setHintingPreference(QFont::PreferFullHinting);
    // This wasn't read until 5.4.
    QTest::newRow("hintingPreference") << font << QDataStream::Qt_5_4;

    font = basicFont;
    font.setStyleName("Regular Black Condensed");
    // This wasn't read until 5.4.
    QTest::newRow("styleName") << font << QDataStream::Qt_5_4;

    font = basicFont;
    font.setCapitalization(QFont::AllUppercase);
    // This wasn't read until 5.6.
    QTest::newRow("capitalization") << font << QDataStream::Qt_5_6;
}

void tst_QFont::serialize()
{
    QFETCH(QFont, font);
    QFETCH(QDataStream::Version, minimumStreamVersion);

    QDataStream stream;
    const int thisVersion = stream.version();

    for (int version = minimumStreamVersion; version <= thisVersion; ++version) {
        QBuffer buffer;
        buffer.open(QIODevice::WriteOnly);
        stream.setDevice(&buffer);
        stream.setVersion(version);
        stream << font;
        buffer.close();

        buffer.open(QIODevice::ReadOnly);
        QFont readFont;
        stream >> readFont;
        QVERIFY2(readFont == font, qPrintable(QString::fromLatin1("Fonts do not compare equal for QDataStream version ") +
            QString::fromLatin1("%1:\nactual: %2\nexpected: %3").arg(version).arg(readFont.toString()).arg(font.toString())));
    }
}

void tst_QFont::deserializeQt515()
{
    QFile file;
    file.setFileName(QFINDTESTDATA("datastream.515"));
    QVERIFY(file.open(QIODevice::ReadOnly));

    QFont font;
    {
        QDataStream stream(&file);
        stream.setVersion(QDataStream::Qt_5_15);
        stream >> font;
    }

    QCOMPARE(font.family(), QStringLiteral("FirstFamily"));
    QCOMPARE(font.families().size(), 3);
    QCOMPARE(font.families().at(0), QStringLiteral("FirstFamily"));
    QCOMPARE(font.families().at(1), QStringLiteral("OtherFamily1"));
    QCOMPARE(font.families().at(2), QStringLiteral("OtherFamily2"));
    QCOMPARE(font.pointSize(), 12);

    QVERIFY(file.reset());
    QByteArray fileContent = file.readAll();
    QByteArray serializedContent;
    {
        QBuffer buffer(&serializedContent);
        QVERIFY(buffer.open(QIODevice::WriteOnly));

        QDataStream stream(&buffer);
        stream.setVersion(QDataStream::Qt_5_15);
        stream << font;
    }

    QCOMPARE(serializedContent, fileContent);

    file.close();
}

void tst_QFont::styleName()
{
#if !defined(Q_OS_MACOS)
    QSKIP("Only tested on Mac");
#else
    QFont font("Helvetica Neue");
    font.setStyleName("UltraLight");

    QCOMPARE(QFontInfo(font).styleName(), QString("UltraLight"));
#endif
}

QString getPlatformGenericFont(const char* genericName)
{
#if defined(Q_OS_UNIX) && !defined(QT_NO_FONTCONFIG) && QT_CONFIG(process)
    QProcess p;
    p.start(QLatin1String("fc-match"), (QStringList() << "-f%{family}" << genericName));
    if (!p.waitForStarted())
        qWarning("fc-match cannot be started: %s", qPrintable(p.errorString()));
    if (p.waitForFinished())
        return QString::fromLatin1(p.readAllStandardOutput());
#endif
    return QLatin1String(genericName);
}

static inline QByteArray msgNotAcceptableFont(const QString &defaultFamily, const QStringList &acceptableFamilies)
{
    QString res = QString::fromLatin1("Font family '%1' is not one of the following acceptable results: ").arg(defaultFamily);
    for (const QString &family : acceptableFamilies)
        res += QLatin1String("\n ") + family;
    return res.toLocal8Bit();
}

Q_DECLARE_METATYPE(QFont::StyleHint)
void tst_QFont::defaultFamily_data()
{
    QTest::addColumn<QFont::StyleHint>("styleHint");
    QTest::addColumn<QStringList>("acceptableFamilies");

    QTest::newRow("serif") << QFont::Serif << (QStringList() << "Times New Roman" << "Times" << "Droid Serif" << getPlatformGenericFont("serif").split(","));
    QTest::newRow("monospace") << QFont::Monospace << (QStringList() << "Courier New" << "Monaco" << "Menlo" << "Droid Sans Mono" << getPlatformGenericFont("monospace").split(","));
    QTest::newRow("cursive") << QFont::Cursive << (QStringList() << "Comic Sans MS" << "Apple Chancery" << "Roboto" << "Droid Sans" << getPlatformGenericFont("cursive").split(","));
    QTest::newRow("fantasy") << QFont::Fantasy << (QStringList() << "Impact" << "Zapfino"  << "Roboto" << "Droid Sans" << getPlatformGenericFont("fantasy").split(","));
    QTest::newRow("sans-serif") << QFont::SansSerif << (QStringList() << "Arial" << "Lucida Grande" << "Helvetica" << "Roboto" << "Droid Sans" << "Segoe UI" << "HarmonyOS Sans" << getPlatformGenericFont("sans-serif").split(","));
}

void tst_QFont::defaultFamily()
{
    QFETCH(QFont::StyleHint, styleHint);
    QFETCH(const QStringList, acceptableFamilies);

    QFont f;
    f.setStyleHint(styleHint);
    const QString familyForHint(f.defaultFamily());

    // it should at least return a family that is available.
    QVERIFY(QFontDatabase::hasFamily(familyForHint));

    bool isAcceptable = false;
    for (const QString &family : acceptableFamilies) {
        if (!familyForHint.compare(family, Qt::CaseInsensitive)) {
            isAcceptable = true;
            break;
        }
    }

#if defined(Q_OS_UNIX) && defined(QT_NO_FONTCONFIG)
    QSKIP("This platform does not support checking for default font acceptability");
#endif

#ifdef Q_PROCESSOR_ARM_32
    if (QTestPrivate::isRunningArmOnX86())
        QEXPECT_FAIL("", "Fails on ARMv7 QEMU (QTQAINFRA-4127)", Continue);
#endif

#ifdef Q_OS_ANDROID
    QEXPECT_FAIL("serif", "QTBUG-69215", Continue);
#endif
#ifdef Q_OS_OHOS
    if (styleHint != QFont::SansSerif)
        QSKIP("OHOS currently falls back to HarmonyOS Sans for all styles");
#endif
    QVERIFY2(isAcceptable, msgNotAcceptableFont(familyForHint, acceptableFamilies));
}

void tst_QFont::toAndFromString()
{
    QFont defaultFont = QGuiApplication::font();
    QString family = defaultFont.family();

    const QStringList stylesList = QFontDatabase::styles(family);
    if (stylesList.size() == 0)
        QSKIP("Default font doesn't have any styles");

    for (const QString &style : stylesList) {
        QFont result;
        QFont initial = QFontDatabase::font(family, style, defaultFont.pointSize());

        result.fromString(initial.toString());

        QCOMPARE(result, initial);
    }
}

void tst_QFont::fromStringCompatibility_data()
{
    QTest::addColumn<bool>("current");
    QTest::addColumn<QString>("description");
    QTest::addColumn<QFont>("font");

    QFont fontFrom515("Times New Roman", 18);
    fontFrom515.setBold(true);
    fontFrom515.setItalic(true);
    fontFrom515.setFixedPitch(true);
    fontFrom515.setStyleName("Regular");
    QTest::addRow("Times New Roman, Qt 5.15") << false << QStringLiteral("Times New Roman,18,-1,5,75,1,0,0,1,0,Regular") << fontFrom515;

    QFont fontFrom60 = fontFrom515;
    fontFrom60.setStyleStrategy(QFont::PreferBitmap);
    fontFrom60.setCapitalization(QFont::AllUppercase);
    fontFrom60.setLetterSpacing(QFont::PercentageSpacing, 150.5);
    fontFrom60.setWordSpacing(2.5);
    fontFrom60.setStretch(50);
    QTest::addRow("Times New Roman, Qt 6.0") << false << QStringLiteral("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular") << fontFrom60;

    QFont fontFrom611 = fontFrom60;
    QTest::addRow("Times New Roman (without font features and variable axes), Qt 6.11") << true << QStringLiteral("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular,0,0") << fontFrom611;

    QFont fontFrom611WithFeatures = fontFrom60;
    fontFrom611WithFeatures.setFeature("frac", 1);
    fontFrom611WithFeatures.setFeature("liga", 0);
    QTest::addRow("Times New Roman (with features), Qt 6.11") << true << QStringLiteral("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular,2,frac=1,liga=0,0") << fontFrom611WithFeatures;

    QFont fontFrom611WithVariableAxes = fontFrom60;
    fontFrom611WithVariableAxes.setVariableAxis("wght", 12.34f);
    QTest::addRow("Times New Roman (with variable axes), Qt 6.11") << true << QStringLiteral("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular,0,1,wght=12.34") << fontFrom611WithVariableAxes;

    QFont fontFrom611WithFontFeaturesAndVariableAxes = fontFrom60;
    fontFrom611WithFontFeaturesAndVariableAxes.setFeature("frac", 1);
    fontFrom611WithFontFeaturesAndVariableAxes.setFeature("liga", 0);
    fontFrom611WithFontFeaturesAndVariableAxes.setVariableAxis("wght", 12.34f);
    QTest::addRow("Times New Roman (with font features and variable axes), Qt 6.11") << true << QStringLiteral("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular,2,frac=1,liga=0,1,wght=12.34") << fontFrom611WithFontFeaturesAndVariableAxes;
}

void tst_QFont::fromStringCompatibility()
{
    // This test verifies that font descriptions from older Qt releases are handled as expected.

    QFETCH(bool, current);
    QFETCH(QString, description);

    QFont font;
    font.fromString(description);
    QTEST(font, "font");

    if (current) {
        QCOMPARE(font.toString(), description);
    }
}

void tst_QFont::fromStringWithoutStyleName()
{
    // This test verifies that the style name will be reset if the from string contains no style.

    const QString fontString(QLatin1String("Times,16,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"));
    {
        QFont font;
        font.fromString("Noto Sans,12,-1,5,50,0,0,0,0,0,Regular");

        QFont from = font;
        from.fromString(fontString);

        QFont clean;
        clean.fromString(fontString);

        QCOMPARE(from, clean);
    }

    const QString fontStringFrom60(
            QLatin1String("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2"));
    {
        QFont font;
        font.fromString("Noto Sans,12,-1,5,50,0,0,0,0,0,Regular");

        QFont from = font;
        from.fromString(fontStringFrom60);

        QFont clean;
        clean.fromString(fontStringFrom60);

        QCOMPARE(from, clean);
    }

    const QString fontStringFrom611(
            QLatin1String("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,,0"));
    {
        QFont font;
        font.fromString("Noto Sans,12,-1,5,50,0,0,0,0,0,Regular");

        QFont from = font;
        from.fromString(fontStringFrom611);

        QFont clean;
        clean.fromString(fontStringFrom611);

        QCOMPARE(from, clean);
    }
}

void tst_QFont::fromStringWithoutFeaturesOrVariableAxes()
{
    // This test verifies that the font feature and the variable axis list will be reset if the string
    // contains no features or variable axes, respectively.

    const QString fontStringWithoutFeaturesAndVariableAxes = QStringLiteral("Noto Sans,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1");
    const QString fontStringWithFeatures = QStringLiteral("Noto Sans,18,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,2,calt=0,frac=1");
    const QString fontStringWithVariableAxes = QStringLiteral("Noto Sans,18,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,1,wght=12.34");

    {
        QFont font;
        font.fromString(fontStringWithFeatures);
        font.fromString(fontStringWithoutFeaturesAndVariableAxes);
        QVERIFY(font.featureTags().isEmpty());
        QVERIFY(font.variableAxisTags().isEmpty());
    }

    {
        QFont font;
        font.fromString(fontStringWithVariableAxes);
        font.fromString(fontStringWithoutFeaturesAndVariableAxes);
        QVERIFY(font.featureTags().isEmpty());
        QVERIFY(font.variableAxisTags().isEmpty());
    }

    {
        QFont font;
        font.fromString(fontStringWithFeatures);
        font.fromString(fontStringWithVariableAxes);
        QVERIFY(font.featureTags().isEmpty());
        QVERIFY(!font.variableAxisTags().isEmpty());
    }

    {
        QFont font;
        font.fromString(fontStringWithVariableAxes);
        font.fromString(fontStringWithFeatures);
        QVERIFY(!font.featureTags().isEmpty());
        QVERIFY(font.variableAxisTags().isEmpty());
    }
}

void tst_QFont::fromDegenerateString_data()
{
    QTest::addColumn<QString>("string");

    QTest::newRow("empty") << QString();
    QTest::newRow("justAComma") << ",";
    QTest::newRow("commasAndSpaces") << " , ,    ";
    QTest::newRow("spaces") << "   ";
    QTest::newRow("spacesTabsAndNewlines") << " \t  \n";
}

void tst_QFont::fromDegenerateString()
{
    QFETCH(QString, string);
    QFont f;
    QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Invalid description.*"));
    QCOMPARE(f.fromString(string), false);
    QCOMPARE(f, QFont());
}

void tst_QFont::sharing()
{
    // QFontCache references the engineData
    int refs_by_cache = 1;

    QFont f;
    f.setStyleHint(QFont::Serif);
    f.exactMatch(); // loads engine
    QCOMPARE(QFontPrivate::get(f)->ref.loadRelaxed(), 1);
    QVERIFY(QFontPrivate::get(f)->engineData);
    QCOMPARE(QFontPrivate::get(f)->engineData->ref.loadRelaxed(), 1 + refs_by_cache);

    QFont f2(f);
    QCOMPARE(QFontPrivate::get(f2), QFontPrivate::get(f));
    QCOMPARE(QFontPrivate::get(f2)->ref.loadRelaxed(), 2);
    QVERIFY(QFontPrivate::get(f2)->engineData);
    QCOMPARE(QFontPrivate::get(f2)->engineData, QFontPrivate::get(f)->engineData);
    QCOMPARE(QFontPrivate::get(f2)->engineData->ref.loadRelaxed(), 1 + refs_by_cache);

    f2.setKerning(!f.kerning());
    QVERIFY(QFontPrivate::get(f2) != QFontPrivate::get(f));
    QCOMPARE(QFontPrivate::get(f2)->ref.loadRelaxed(), 1);
    QVERIFY(QFontPrivate::get(f2)->engineData);
    QCOMPARE(QFontPrivate::get(f2)->engineData, QFontPrivate::get(f)->engineData);
    QCOMPARE(QFontPrivate::get(f2)->engineData->ref.loadRelaxed(), 2 + refs_by_cache);

    f2 = f;
    QCOMPARE(QFontPrivate::get(f2), QFontPrivate::get(f));
    QCOMPARE(QFontPrivate::get(f2)->ref.loadRelaxed(), 2);
    QVERIFY(QFontPrivate::get(f2)->engineData);
    QCOMPARE(QFontPrivate::get(f2)->engineData, QFontPrivate::get(f)->engineData);
    QCOMPARE(QFontPrivate::get(f2)->engineData->ref.loadRelaxed(), 1 + refs_by_cache);

    if (f.pointSize() > 0)
        f2.setPointSize(f.pointSize() * 2 / 3);
    else
        f2.setPixelSize(f.pixelSize() * 2 / 3);
    QVERIFY(QFontPrivate::get(f2) != QFontPrivate::get(f));
    QCOMPARE(QFontPrivate::get(f2)->ref.loadRelaxed(), 1);
    QVERIFY(!QFontPrivate::get(f2)->engineData);
    QVERIFY(QFontPrivate::get(f2)->engineData != QFontPrivate::get(f)->engineData);
}

void tst_QFont::familyNameWithCommaQuote_data()
{
    QTest::addColumn<QString>("enteredFamilyName");
    QTest::addColumn<QString>("familyName");
    QTest::addColumn<QString>("chosenFamilyName");

    const QString standardFont(QFont().defaultFamily());
    if (standardFont.isEmpty())
        QSKIP("No default font available on the system");
    const QString weirdFont(QLatin1String("'My, weird'' font name',"));
    const QString bogusFont(QLatin1String("BogusFont"));
    const QString commaSeparated(standardFont + QLatin1String(",Times New Roman"));
    const QString commaSeparatedWeird(weirdFont + QLatin1String(",") + standardFont);
    const QString commaSeparatedBogus(bogusFont +  QLatin1String(",") + standardFont);

    QTest::newRow("standard") << standardFont << standardFont << standardFont;
    QTest::newRow("weird") << weirdFont << QString("'My") << standardFont;
    QTest::newRow("commaSeparated") << commaSeparated << standardFont << standardFont;
    QTest::newRow("commaSeparatedWeird") << commaSeparatedWeird << QString("'My") << standardFont;
    QTest::newRow("commaSeparatedBogus") << commaSeparatedBogus << bogusFont << standardFont;
}

void tst_QFont::familyNameWithCommaQuote()
{
    QFETCH(QString, familyName);
    QFETCH(QString, chosenFamilyName);

    const int weirdFontId = QFontDatabase::addApplicationFont(":/weirdfont.otf");

    QVERIFY(weirdFontId != -1);
    QFont f(familyName);
    QCOMPARE(f.family(), familyName);
    QCOMPARE(QFontInfo(f).family(), chosenFamilyName);

    QFontDatabase::removeApplicationFont(weirdFontId);
}

void tst_QFont::setFamilies_data()
{
    QTest::addColumn<QStringList>("families");
    QTest::addColumn<QString>("chosenFamilyName");

    const QString weirdFont(QLatin1String("'My, weird'' font name',"));
    const QString standardFont(QFont().defaultFamily());
    if (standardFont.isEmpty())
        QSKIP("No default font available on the system");

    QTest::newRow("emptyFamily") << (QStringList()) << QString();
    QTest::newRow("standard") << (QStringList() << standardFont) << standardFont;
    QTest::newRow("weird") << (QStringList() << weirdFont) << weirdFont;
    QTest::newRow("standard-weird") << (QStringList() << standardFont << weirdFont) << standardFont;
    QTest::newRow("weird-standard") << (QStringList() << weirdFont << standardFont) << weirdFont;
    QTest::newRow("nonexist-weird") << (QStringList() << "NonExistentFont" << weirdFont) << weirdFont;
}

void tst_QFont::setFamilies()
{
    QFETCH(QStringList, families);
    QFETCH(QString, chosenFamilyName);

    const int weirdFontId = QFontDatabase::addApplicationFont(":/weirdfont.otf");

    QVERIFY(weirdFontId != -1);
    QFont f;
    f.setFamilies(families);
    if (!chosenFamilyName.isEmpty()) // Only check when it is not empty
        QCOMPARE(QFontInfo(f).family(), chosenFamilyName);

    QFontDatabase::removeApplicationFont(weirdFontId);
}

void tst_QFont::setFamiliesAndFamily_data()
{
    QTest::addColumn<QStringList>("families");
    QTest::addColumn<QString>("family");
    QTest::addColumn<QString>("chosenFamilyName");

    const QString weirdFont(QLatin1String("'My, weird'' font name',"));
    const QString defaultFont(QFont().defaultFamily());
    if (defaultFont.isEmpty())
        QSKIP("No default font available on the system");

    const QString timesFont(QLatin1String("Times"));
    const QString nonExistFont(QLatin1String("NonExistentFont"));

    QTest::newRow("emptyFamily") << (QStringList()) << QString() << QString();
    QTest::newRow("firstInFamilies") << (QStringList() << defaultFont << timesFont) << weirdFont << defaultFont;
    QTest::newRow("secondInFamilies") << (QStringList() << nonExistFont << weirdFont) << defaultFont << weirdFont;
    QTest::newRow("family") << (QStringList() << nonExistFont) << defaultFont << defaultFont;
}

void tst_QFont::setFamiliesAndFamily()
{
    QFETCH(QStringList, families);
    QFETCH(QString, family);
    QFETCH(QString, chosenFamilyName);

    const int weirdFontId = QFontDatabase::addApplicationFont(":/weirdfont.otf");

    QVERIFY(weirdFontId != -1);
    QFont f;
    f.setFamily(family);
    f.setFamilies(families);
    if (!family.isEmpty()) // Only check when it is not empty
        QCOMPARE(QFontInfo(f).family(), chosenFamilyName);

    QFontDatabase::removeApplicationFont(weirdFontId);
}

void tst_QFont::featureAccessors()
{
    const QFont::Tag abcdTag("abcd");
    QCOMPARE(abcdTag.toString(), "abcd");
    QVERIFY(abcdTag.isValid());

    QFont font;
    QVERIFY(font.featureTags().isEmpty());
    font.setFeature("abcd", 0xc0ffee);

    QVERIFY(font.isFeatureSet(abcdTag));
    QVERIFY(!font.isFeatureSet("bcde"));
    QCOMPARE(font.featureTags().size(), 1);
    QCOMPARE(font.featureTags().first(), abcdTag);
    QCOMPARE(font.featureTags().first(), "abcd");
    QCOMPARE(font.featureValue(abcdTag), 0xc0ffeeU);
    QCOMPARE(font.featureValue("bcde"), 0U);
    font.setFeature(abcdTag, 0xf00d);
    QCOMPARE(font.featureTags().size(), 1);
    QCOMPARE(font.featureValue(abcdTag), 0xf00dU);

    QFont::Tag invalidTag;
    QVERIFY(!invalidTag.isValid());
    font.setFeature(invalidTag, 0xcaca0);
    QVERIFY(!font.isFeatureSet(invalidTag));
    QCOMPARE(font.featureTags().size(), 1);
    QFont font2 = font;

    font.unsetFeature("abcd");
    QVERIFY(!font.isFeatureSet("abcd"));
    QVERIFY(font.featureTags().isEmpty());

    QVERIFY(font2.isFeatureSet("abcd"));
    font2.clearFeatures();
    QVERIFY(font.featureTags().isEmpty());

    // various constructor compile tests
    QFont::Tag tag;
    tag = QFont::Tag("1234");
    QVERIFY(QFont::Tag::fromString(QByteArray("abcd")));
    QVERIFY(QFont::Tag::fromString(u"frac"_s));

    // named constructors with invalid input
    QTest::ignoreMessage(QtWarningMsg, "The tag name must be exactly 4 characters long!");
    QVERIFY(!QFont::Tag::fromString(u"fraction"_s));
    QVERIFY(!QFont::Tag::fromValue(0));
    QVERIFY(QFont::Tag::fromValue(abcdTag.value()));

    enum Features {
        Frac = QFont::Tag("frac").value()
    };
}

void tst_QFont::tagCompares_data()
{
    QTestPrivate::testAllComparisonOperatorsCompile<QFont::Tag>();

    QTest::addColumn<QFont::Tag>("lhs");
    QTest::addColumn<QFont::Tag>("rhs");
    QTest::addColumn<Qt::strong_ordering>("expectedOrder");

    auto row = [](QFont::Tag left, QFont::Tag right) {
        QTest::addRow("%s<=>%s", left.toString().constData(), right.toString().constData())
            << left << right << Qt::compareThreeWay(left.value(), right.value());
    };
    row("frac", "wght");
}

void tst_QFont::tagCompares()
{
    QFETCH(QFont::Tag, lhs);
    QFETCH(QFont::Tag, rhs);
    QFETCH(Qt::strong_ordering, expectedOrder);

    QVERIFY(comparesEqual(lhs, lhs));
    QCOMPARE(compareThreeWay(lhs, rhs), expectedOrder);
}

void tst_QFont::variableAxes()
{
    {
        QPlatformFontDatabase *pfdb = QGuiApplicationPrivate::platformIntegration()->fontDatabase();
        if (!pfdb->supportsVariableApplicationFonts())
            QSKIP("Variable application fonts not supported on this platform");
    }

    int id = QFontDatabase::addApplicationFont(m_testFontVariable);
    if (id == -1)
        QSKIP("Application fonts are not supported on this system");
    auto cleanup = qScopeGuard([&id] {
        QFontDatabase::removeApplicationFont(id);
    });

    QString family = QFontDatabase::applicationFontFamilies(id).first();
    QFontInfo fontInfo(QFont(family, 12));

    QList<QFontVariableAxis> variableAxes = fontInfo.variableAxes();
    QCOMPARE(variableAxes.size(), 1);

    const QFontVariableAxis &variableAxis = variableAxes.first();
    QCOMPARE(variableAxis.name(), QStringLiteral("Weight"));
    QCOMPARE(variableAxis.tag(), QFont::Tag::fromString("wght"));
    QCOMPARE(variableAxis.defaultValue(), 400.0);
    QCOMPARE(variableAxis.minimumValue(), 400.0);
    QCOMPARE(variableAxis.maximumValue(), 900.0);
}

QTEST_MAIN(tst_QFont)
#include "tst_qfont.moc"
