1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qglobal.h"
41
42#include <sys/param.h>
43
44#if defined(Q_OS_MACOS)
45#import <AppKit/AppKit.h>
46#import <IOKit/graphics/IOGraphicsLib.h>
47#elif defined(QT_PLATFORM_UIKIT)
48#import <UIKit/UIFont.h>
49#endif
50
51#include <QtCore/qelapsedtimer.h>
52
53#include "qcoretextfontdatabase_p.h"
54#include "qfontengine_coretext_p.h"
55#if QT_CONFIG(settings)
56#include <QtCore/QSettings>
57#endif
58#include <QtCore/QtEndian>
59#ifndef QT_NO_FREETYPE
60#include <QtFontDatabaseSupport/private/qfontengine_ft_p.h>
61#endif
62
63QT_BEGIN_NAMESPACE
64
65// this could become a list of all languages used for each writing
66// system, instead of using the single most common language.
67static const char *languageForWritingSystem[] = {
68    0,     // Any
69    "en",  // Latin
70    "el",  // Greek
71    "ru",  // Cyrillic
72    "hy",  // Armenian
73    "he",  // Hebrew
74    "ar",  // Arabic
75    "syr", // Syriac
76    "div", // Thaana
77    "hi",  // Devanagari
78    "bn",  // Bengali
79    "pa",  // Gurmukhi
80    "gu",  // Gujarati
81    "or",  // Oriya
82    "ta",  // Tamil
83    "te",  // Telugu
84    "kn",  // Kannada
85    "ml",  // Malayalam
86    "si",  // Sinhala
87    "th",  // Thai
88    "lo",  // Lao
89    "bo",  // Tibetan
90    "my",  // Myanmar
91    "ka",  // Georgian
92    "km",  // Khmer
93    "zh-Hans", // SimplifiedChinese
94    "zh-Hant", // TraditionalChinese
95    "ja",  // Japanese
96    "ko",  // Korean
97    "vi",  // Vietnamese
98    0, // Symbol
99    "sga", // Ogham
100    "non", // Runic
101    "man" // N'Ko
102};
103enum { LanguageCount = sizeof(languageForWritingSystem) / sizeof(const char *) };
104
105QCoreTextFontDatabase::QCoreTextFontDatabase()
106    : m_hasPopulatedAliases(false)
107{
108}
109
110QCoreTextFontDatabase::~QCoreTextFontDatabase()
111{
112    for (CTFontDescriptorRef ref : qAsConst(m_systemFontDescriptors))
113        CFRelease(ref);
114}
115
116void QCoreTextFontDatabase::populateFontDatabase()
117{
118    qCDebug(lcQpaFonts) << "Populating font database...";
119    QElapsedTimer elapsed;
120    if (lcQpaFonts().isDebugEnabled())
121        elapsed.start();
122
123    QCFType<CFArrayRef> familyNames = CTFontManagerCopyAvailableFontFamilyNames();
124    for (NSString *familyName in familyNames.as<const NSArray *>())
125        QPlatformFontDatabase::registerFontFamily(QString::fromNSString(familyName));
126
127    qCDebug(lcQpaFonts) << "Populating available families took" << elapsed.restart() << "ms";
128
129    // Force creating the theme fonts to get the descriptors in m_systemFontDescriptors
130    if (m_themeFonts.isEmpty())
131        (void)themeFonts();
132
133    qCDebug(lcQpaFonts) << "Resolving theme fonts took" << elapsed.restart() << "ms";
134
135    Q_FOREACH (CTFontDescriptorRef fontDesc, m_systemFontDescriptors)
136        populateFromDescriptor(fontDesc);
137
138    qCDebug(lcQpaFonts) << "Populating system descriptors took" << elapsed.restart() << "ms";
139
140    Q_ASSERT(!m_hasPopulatedAliases);
141}
142
143bool QCoreTextFontDatabase::populateFamilyAliases(const QString &missingFamily)
144{
145#if defined(Q_OS_MACOS)
146    if (m_hasPopulatedAliases)
147        return false;
148
149    // There's no API to go from a localized family name to its non-localized
150    // name, so we have to resort to enumerating all the available fonts and
151    // doing a reverse lookup.
152
153    qCDebug(lcQpaFonts) << "Populating family aliases...";
154    QElapsedTimer elapsed;
155    elapsed.start();
156
157    QString nonLocalizedMatch;
158    QCFType<CFArrayRef> familyNames = CTFontManagerCopyAvailableFontFamilyNames();
159    NSFontManager *fontManager = NSFontManager.sharedFontManager;
160    for (NSString *familyName in familyNames.as<const NSArray *>()) {
161        NSString *localizedFamilyName = [fontManager localizedNameForFamily:familyName face:nil];
162        if (![localizedFamilyName isEqual:familyName]) {
163            QString nonLocalizedFamily = QString::fromNSString(familyName);
164            QString localizedFamily = QString::fromNSString(localizedFamilyName);
165            QPlatformFontDatabase::registerAliasToFontFamily(nonLocalizedFamily, localizedFamily);
166            if (localizedFamily == missingFamily)
167                nonLocalizedMatch = nonLocalizedFamily;
168        }
169    }
170    m_hasPopulatedAliases = true;
171
172    if (lcQpaFonts().isWarningEnabled()) {
173        QString warningMessage;
174        QDebug msg(&warningMessage);
175
176        msg << "Populating font family aliases took" << elapsed.restart() << "ms.";
177        if (!nonLocalizedMatch.isNull())
178            msg << "Replace uses of" << missingFamily << "with its non-localized name" << nonLocalizedMatch;
179        else
180            msg << "Replace uses of missing font family" << missingFamily << "with one that exists";
181        msg << "to avoid this cost.";
182
183        qCWarning(lcQpaFonts) << qPrintable(warningMessage);
184    }
185
186    return true;
187#else
188    Q_UNUSED(missingFamily);
189    return false;
190#endif
191}
192
193void QCoreTextFontDatabase::populateFamily(const QString &familyName)
194{
195    QCFType<CFMutableDictionaryRef> attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
196    CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, QCFString(familyName));
197    QCFType<CTFontDescriptorRef> nameOnlyDescriptor = CTFontDescriptorCreateWithAttributes(attributes);
198
199    // A single family might match several different fonts with different styles eg.
200    QCFType<CFArrayRef> matchingFonts = (CFArrayRef) CTFontDescriptorCreateMatchingFontDescriptors(nameOnlyDescriptor, 0);
201    if (!matchingFonts) {
202        qCWarning(lcQpaFonts) << "QCoreTextFontDatabase: Found no matching fonts for family" << familyName;
203        return;
204    }
205
206    const int numFonts = CFArrayGetCount(matchingFonts);
207    for (int i = 0; i < numFonts; ++i)
208        populateFromDescriptor(CTFontDescriptorRef(CFArrayGetValueAtIndex(matchingFonts, i)), familyName);
209}
210
211void QCoreTextFontDatabase::invalidate()
212{
213    m_hasPopulatedAliases = false;
214}
215
216struct FontDescription {
217    QCFString familyName;
218    QCFString styleName;
219    QString foundryName;
220    QFont::Weight weight;
221    QFont::Style style;
222    QFont::Stretch stretch;
223    qreal pointSize;
224    bool fixedPitch;
225    QSupportedWritingSystems writingSystems;
226};
227
228#ifndef QT_NO_DEBUG_STREAM
229Q_DECL_UNUSED static inline QDebug operator<<(QDebug debug, const FontDescription &fd)
230{
231    QDebugStateSaver saver(debug);
232    return debug.nospace() << "FontDescription("
233        << "familyName=" << QString(fd.familyName)
234        << ", styleName=" << QString(fd.styleName)
235        << ", foundry=" << fd.foundryName
236        << ", weight=" << fd.weight
237        << ", style=" << fd.style
238        << ", stretch=" << fd.stretch
239        << ", pointSize=" << fd.pointSize
240        << ", fixedPitch=" << fd.fixedPitch
241        << ", writingSystems=" << fd.writingSystems
242    << ")";
243}
244#endif
245
246static void getFontDescription(CTFontDescriptorRef font, FontDescription *fd)
247{
248    QCFType<CFDictionaryRef> styles = (CFDictionaryRef) CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute);
249
250    fd->foundryName = QStringLiteral("CoreText");
251    fd->familyName = (CFStringRef) CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute);
252    fd->styleName = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute);
253    fd->weight = QFont::Normal;
254    fd->style = QFont::StyleNormal;
255    fd->stretch = QFont::Unstretched;
256    fd->fixedPitch = false;
257
258    if (QCFType<CTFontRef> tempFont = CTFontCreateWithFontDescriptor(font, 0.0, 0)) {
259        uint tag = MAKE_TAG('O', 'S', '/', '2');
260        CTFontRef tempFontRef = tempFont;
261        void *userData = reinterpret_cast<void *>(&tempFontRef);
262        uint length = 128;
263        QVarLengthArray<uchar, 128> os2Table(length);
264        if (QCoreTextFontEngine::ct_getSfntTable(userData, tag, os2Table.data(), &length) && length >= 86) {
265            if (length > uint(os2Table.length())) {
266                os2Table.resize(length);
267                if (!QCoreTextFontEngine::ct_getSfntTable(userData, tag, os2Table.data(), &length))
268                    Q_UNREACHABLE();
269                Q_ASSERT(length >= 86);
270            }
271            quint32 unicodeRange[4] = {
272                qFromBigEndian<quint32>(os2Table.data() + 42),
273                qFromBigEndian<quint32>(os2Table.data() + 46),
274                qFromBigEndian<quint32>(os2Table.data() + 50),
275                qFromBigEndian<quint32>(os2Table.data() + 54)
276            };
277            quint32 codePageRange[2] = {
278                qFromBigEndian<quint32>(os2Table.data() + 78),
279                qFromBigEndian<quint32>(os2Table.data() + 82)
280            };
281            fd->writingSystems = QPlatformFontDatabase::writingSystemsFromTrueTypeBits(unicodeRange, codePageRange);
282        }
283    }
284
285    if (styles) {
286        if (CFNumberRef weightValue = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontWeightTrait)) {
287            double normalizedWeight;
288            if (CFNumberGetValue(weightValue, kCFNumberFloat64Type, &normalizedWeight))
289                fd->weight = QCoreTextFontEngine::qtWeightFromCFWeight(float(normalizedWeight));
290        }
291        if (CFNumberRef italic = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontSlantTrait)) {
292            double d;
293            if (CFNumberGetValue(italic, kCFNumberDoubleType, &d)) {
294                if (d > 0.0)
295                    fd->style = QFont::StyleItalic;
296            }
297        }
298        if (CFNumberRef symbolic = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontSymbolicTrait)) {
299            int d;
300            if (CFNumberGetValue(symbolic, kCFNumberSInt32Type, &d)) {
301                if (d & kCTFontMonoSpaceTrait)
302                    fd->fixedPitch = true;
303                if (d & kCTFontExpandedTrait)
304                    fd->stretch = QFont::Expanded;
305                else if (d & kCTFontCondensedTrait)
306                    fd->stretch = QFont::Condensed;
307            }
308        }
309    }
310
311    if (QCFType<CFNumberRef> size = (CFNumberRef) CTFontDescriptorCopyAttribute(font, kCTFontSizeAttribute)) {
312        if (CFNumberIsFloatType(size)) {
313            double d;
314            CFNumberGetValue(size, kCFNumberDoubleType, &d);
315            fd->pointSize = d;
316        } else {
317            int i;
318            CFNumberGetValue(size, kCFNumberIntType, &i);
319            fd->pointSize = i;
320        }
321    }
322
323    if (QCFType<CFArrayRef> languages = (CFArrayRef) CTFontDescriptorCopyAttribute(font, kCTFontLanguagesAttribute)) {
324        CFIndex length = CFArrayGetCount(languages);
325        for (int i = 1; i < LanguageCount; ++i) {
326            if (!languageForWritingSystem[i])
327                continue;
328            QCFString lang = CFStringCreateWithCString(NULL, languageForWritingSystem[i], kCFStringEncodingASCII);
329            if (CFArrayContainsValue(languages, CFRangeMake(0, length), lang))
330                fd->writingSystems.setSupported(QFontDatabase::WritingSystem(i));
331        }
332    }
333}
334
335void QCoreTextFontDatabase::populateFromDescriptor(CTFontDescriptorRef font, const QString &familyName)
336{
337    FontDescription fd;
338    getFontDescription(font, &fd);
339
340    // Note: The familyName we are registering, and the family name of the font descriptor, may not
341    // match, as CTFontDescriptorCreateMatchingFontDescriptors will return descriptors for replacement
342    // fonts if a font family does not have any fonts available on the system.
343    QString family = !familyName.isNull() ? familyName : static_cast<QString>(fd.familyName);
344
345    CFRetain(font);
346    QPlatformFontDatabase::registerFont(family, fd.styleName, fd.foundryName, fd.weight, fd.style, fd.stretch,
347            true /* antialiased */, true /* scalable */, 0 /* pixelSize, ignored as font is scalable */,
348            fd.fixedPitch, fd.writingSystems, (void *)font);
349}
350
351static NSString * const kQtFontDataAttribute = @"QtFontDataAttribute";
352
353template <typename T>
354T *descriptorAttribute(CTFontDescriptorRef descriptor, CFStringRef name)
355{
356    return [static_cast<T *>(CTFontDescriptorCopyAttribute(descriptor, name)) autorelease];
357}
358
359void QCoreTextFontDatabase::releaseHandle(void *handle)
360{
361    CTFontDescriptorRef descriptor = static_cast<CTFontDescriptorRef>(handle);
362    if (NSValue *fontDataValue = descriptorAttribute<NSValue>(descriptor, (CFStringRef)kQtFontDataAttribute)) {
363        QByteArray *fontData = static_cast<QByteArray *>(fontDataValue.pointerValue);
364        delete fontData;
365    }
366    CFRelease(descriptor);
367}
368
369extern CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef);
370
371template <>
372QFontEngine *QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>::fontEngine(const QFontDef &fontDef, void *usrPtr)
373{
374    QCFType<CTFontDescriptorRef> descriptor = QCFType<CTFontDescriptorRef>::constructFromGet(
375        static_cast<CTFontDescriptorRef>(usrPtr));
376
377    // Since we do not pass in the destination DPI to CoreText when making
378    // the font, we need to pass in a point size which is scaled to include
379    // the DPI. The default DPI for the screen is 72, thus the scale factor
380    // is destinationDpi / 72, but since pixelSize = pointSize / 72 * dpi,
381    // the pixelSize is actually the scaled point size for the destination
382    // DPI, and we can use that directly.
383    qreal scaledPointSize = fontDef.pixelSize;
384
385    CGAffineTransform matrix = qt_transform_from_fontdef(fontDef);
386    if (QCFType<CTFontRef> font = CTFontCreateWithFontDescriptor(descriptor, scaledPointSize, &matrix))
387        return new QCoreTextFontEngine(font, fontDef);
388
389    return nullptr;
390}
391
392#ifndef QT_NO_FREETYPE
393template <>
394QFontEngine *QCoreTextFontDatabaseEngineFactory<QFontEngineFT>::fontEngine(const QFontDef &fontDef, void *usrPtr)
395{
396    CTFontDescriptorRef descriptor = static_cast<CTFontDescriptorRef>(usrPtr);
397
398    if (NSValue *fontDataValue = descriptorAttribute<NSValue>(descriptor, (CFStringRef)kQtFontDataAttribute)) {
399        QByteArray *fontData = static_cast<QByteArray *>(fontDataValue.pointerValue);
400        return QFontEngineFT::create(*fontData, fontDef.pixelSize,
401            static_cast<QFont::HintingPreference>(fontDef.hintingPreference));
402    } else if (NSURL *url = descriptorAttribute<NSURL>(descriptor, kCTFontURLAttribute)) {
403        Q_ASSERT(url.fileURL);
404        QFontEngine::FaceId faceId;
405        faceId.filename = QString::fromNSString(url.path).toUtf8();
406        return QFontEngineFT::create(fontDef, faceId);
407    }
408    // We end up here with a descriptor does not contain Qt font data or kCTFontURLAttribute.
409    // Since the FT engine can't deal with a descriptor with just a NSFontNameAttribute,
410    // we should return nullptr.
411    return nullptr;
412}
413#endif
414
415template <class T>
416QFontEngine *QCoreTextFontDatabaseEngineFactory<T>::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
417{
418    return T::create(fontData, pixelSize, hintingPreference);
419}
420
421// Explicitly instantiate so that we don't need the plugin to involve FreeType
422template class QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>;
423#ifndef QT_NO_FREETYPE
424template class QCoreTextFontDatabaseEngineFactory<QFontEngineFT>;
425#endif
426
427CTFontDescriptorRef descriptorForFamily(const QString &familyName)
428{
429    return CTFontDescriptorCreateWithAttributes(CFDictionaryRef(@{
430        (id)kCTFontFamilyNameAttribute: familyName.toNSString()
431    }));
432}
433
434CTFontDescriptorRef descriptorForFamily(const char *familyName)
435{
436    return descriptorForFamily(QString::fromLatin1(familyName));
437}
438
439CFArrayRef fallbacksForDescriptor(CTFontDescriptorRef descriptor)
440{
441    QCFType<CTFontRef> font = CTFontCreateWithFontDescriptor(descriptor, 0.0, nullptr);
442    if (!font) {
443        qCWarning(lcQpaFonts) << "Failed to create fallback font for" << descriptor;
444        return nullptr;
445    }
446
447    CFArrayRef cascadeList = CFArrayRef(CTFontCopyDefaultCascadeListForLanguages(font,
448        (CFArrayRef)[NSUserDefaults.standardUserDefaults stringArrayForKey:@"AppleLanguages"]));
449
450    if (!cascadeList) {
451        qCWarning(lcQpaFonts) << "Failed to create fallback cascade list for" << descriptor;
452        return nullptr;
453    }
454
455    return cascadeList;
456}
457
458CFArrayRef QCoreTextFontDatabase::fallbacksForFamily(const QString &family)
459{
460    if (family.isEmpty())
461        return nullptr;
462
463    QCFType<CTFontDescriptorRef> fontDescriptor = descriptorForFamily(family);
464    if (!fontDescriptor) {
465        qCWarning(lcQpaFonts) << "Failed to create fallback font descriptor for" << family;
466        return nullptr;
467    }
468
469    // If the font is not available we want to fall back to the style hint.
470    // By creating a matching font descriptor we can verify whether the font
471    // is available or not, and avoid CTFontCreateWithFontDescriptor picking
472    // a default font for us based on incomplete information.
473    fontDescriptor = CTFontDescriptorCreateMatchingFontDescriptor(fontDescriptor, 0);
474    if (!fontDescriptor)
475        return nullptr;
476
477    return fallbacksForDescriptor(fontDescriptor);
478}
479
480CTFontDescriptorRef descriptorForFontType(CTFontUIFontType uiType)
481{
482    static const CGFloat kDefaultSizeForRequestedUIType = 0.0;
483    QCFType<CTFontRef> ctFont = CTFontCreateUIFontForLanguage(
484        uiType, kDefaultSizeForRequestedUIType, nullptr);
485    return CTFontCopyFontDescriptor(ctFont);
486}
487
488CTFontDescriptorRef descriptorForStyle(QFont::StyleHint styleHint)
489{
490    switch (styleHint) {
491        case QFont::SansSerif: return descriptorForFamily("Helvetica");
492        case QFont::Serif: return descriptorForFamily("Times New Roman");
493        case QFont::Monospace: return descriptorForFamily("Menlo");
494#ifdef Q_OS_MACOS
495        case QFont::Cursive: return descriptorForFamily("Apple Chancery");
496#endif
497        case QFont::Fantasy: return descriptorForFamily("Zapfino");
498        case QFont::TypeWriter: return descriptorForFamily("American Typewriter");
499        case QFont::AnyStyle: Q_FALLTHROUGH();
500        case QFont::System: return descriptorForFontType(kCTFontUIFontSystem);
501        default: return nullptr; // No matching font on this platform
502    }
503}
504
505QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
506{
507    Q_UNUSED(style);
508
509    qCDebug(lcQpaFonts).nospace() << "Resolving fallbacks families for"
510        << (!family.isEmpty() ? qPrintable(QLatin1String(" family '%1' with").arg(family)) : "")
511        << " style hint " << styleHint;
512
513    QMacAutoReleasePool pool;
514
515    QStringList fallbackList;
516
517    QCFType<CFArrayRef> fallbackFonts = fallbacksForFamily(family);
518    if (!fallbackFonts || !CFArrayGetCount(fallbackFonts)) {
519        // We were not able to find a fallback for the specific family,
520        // or the family was empty, so we fall back to the style hint.
521        if (!family.isEmpty())
522            qCDebug(lcQpaFonts) << "No fallbacks found. Using style hint instead";
523
524        if (QCFType<CTFontDescriptorRef> styleDescriptor = descriptorForStyle(styleHint)) {
525            CFMutableArrayRef tmp = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
526            CFArrayAppendValue(tmp, styleDescriptor);
527            QCFType<CFArrayRef> styleFallbacks = fallbacksForDescriptor(styleDescriptor);
528            CFArrayAppendArray(tmp, styleFallbacks, CFRangeMake(0, CFArrayGetCount(styleFallbacks)));
529            fallbackFonts = tmp;
530        }
531    }
532
533    if (!fallbackFonts)
534        return fallbackList;
535
536    const int numberOfFallbacks = CFArrayGetCount(fallbackFonts);
537    for (int i = 0; i < numberOfFallbacks; ++i) {
538        auto fallbackDescriptor = CTFontDescriptorRef(CFArrayGetValueAtIndex(fallbackFonts, i));
539        auto fallbackFamilyName = QCFString(CTFontDescriptorCopyAttribute(fallbackDescriptor, kCTFontFamilyNameAttribute));
540
541        if (!isFamilyPopulated(fallbackFamilyName)) {
542            // We need to populate, or at least register the fallback fonts,
543            // otherwise the Qt font database may not know they exist.
544            if (isPrivateFontFamily(fallbackFamilyName))
545                const_cast<QCoreTextFontDatabase *>(this)->populateFromDescriptor(fallbackDescriptor);
546            else
547                registerFontFamily(fallbackFamilyName);
548        }
549
550        fallbackList.append(fallbackFamilyName);
551    }
552
553    // Some fallback fonts will have have an order in the list returned
554    // by Core Text that would indicate they should be preferred for e.g.
555    // Arabic, or Emoji, while in reality only supporting a tiny subset
556    // of the required glyphs, or representing them by question marks.
557    // Move these to the end, so that the proper fonts are preferred.
558    for (const char *family : { ".Apple Symbols Fallback", ".Noto Sans Universal" }) {
559        int index = fallbackList.indexOf(QLatin1String(family));
560        if (index >= 0)
561            fallbackList.move(index, fallbackList.size() - 1);
562    }
563
564#if defined(Q_OS_MACOS)
565    // Since we are only returning a list of default fonts for the current language, we do not
566    // cover all Unicode completely. This was especially an issue for some of the common script
567    // symbols such as mathematical symbols, currency or geometric shapes. To minimize the risk
568    // of missing glyphs, we add Arial Unicode MS as a final fail safe, since this covers most
569    // of Unicode 2.1.
570    if (!fallbackList.contains(QStringLiteral("Arial Unicode MS")))
571        fallbackList.append(QStringLiteral("Arial Unicode MS"));
572    // Since some symbols (specifically Braille) are not in Arial Unicode MS, we
573    // add Apple Symbols to cover those too.
574    if (!fallbackList.contains(QStringLiteral("Apple Symbols")))
575        fallbackList.append(QStringLiteral("Apple Symbols"));
576#endif
577
578    extern QStringList qt_sort_families_by_writing_system(QChar::Script, const QStringList &);
579    fallbackList = qt_sort_families_by_writing_system(script, fallbackList);
580
581    qCDebug(lcQpaFonts).nospace() << "Fallback families ordered by script " << script << ": " << fallbackList;
582
583    return fallbackList;
584}
585
586QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName)
587{
588    QCFType<CFArrayRef> fonts;
589
590    if (!fontData.isEmpty()) {
591        QCFType<CFDataRef> fontDataReference = fontData.toRawCFData();
592        if (QCFType<CTFontDescriptorRef> descriptor = CTFontManagerCreateFontDescriptorFromData(fontDataReference)) {
593            // There's no way to get the data back out of a font descriptor created with
594            // CTFontManagerCreateFontDescriptorFromData, so we attach the data manually.
595            NSDictionary *attributes = @{ kQtFontDataAttribute : [NSValue valueWithPointer:new QByteArray(fontData)] };
596            descriptor = CTFontDescriptorCreateCopyWithAttributes(descriptor, (CFDictionaryRef)attributes);
597            CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
598            CFArrayAppendValue(array, descriptor);
599            fonts = array;
600        }
601    } else {
602        QCFType<CFURLRef> fontURL = QUrl::fromLocalFile(fileName).toCFURL();
603        fonts = CTFontManagerCreateFontDescriptorsFromURL(fontURL);
604    }
605
606    if (!fonts)
607        return QStringList();
608
609    QStringList families;
610    const int numFonts = CFArrayGetCount(fonts);
611    for (int i = 0; i < numFonts; ++i) {
612        CTFontDescriptorRef fontDescriptor = CTFontDescriptorRef(CFArrayGetValueAtIndex(fonts, i));
613        populateFromDescriptor(fontDescriptor);
614        QCFType<CFStringRef> familyName = CFStringRef(CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontFamilyNameAttribute));
615        families.append(QString::fromCFString(familyName));
616    }
617
618    // Note: We don't do font matching via CoreText for application fonts, so we don't
619    // need to enable font matching for them via CTFontManagerEnableFontDescriptors.
620
621    return families;
622}
623
624bool QCoreTextFontDatabase::isPrivateFontFamily(const QString &family) const
625{
626    if (family.startsWith(QLatin1Char('.')) || family == QLatin1String("LastResort"))
627        return true;
628
629    return QPlatformFontDatabase::isPrivateFontFamily(family);
630}
631
632static CTFontUIFontType fontTypeFromTheme(QPlatformTheme::Font f)
633{
634    switch (f) {
635    case QPlatformTheme::SystemFont:
636        return kCTFontUIFontSystem;
637
638    case QPlatformTheme::MenuFont:
639    case QPlatformTheme::MenuBarFont:
640    case QPlatformTheme::MenuItemFont:
641        return kCTFontUIFontMenuItem;
642
643    case QPlatformTheme::MessageBoxFont:
644        return kCTFontUIFontEmphasizedSystem;
645
646    case QPlatformTheme::LabelFont:
647        return kCTFontUIFontSystem;
648
649    case QPlatformTheme::TipLabelFont:
650        return kCTFontUIFontToolTip;
651
652    case QPlatformTheme::StatusBarFont:
653        return kCTFontUIFontSystem;
654
655    case QPlatformTheme::TitleBarFont:
656        return kCTFontUIFontWindowTitle;
657
658    case QPlatformTheme::MdiSubWindowTitleFont:
659        return kCTFontUIFontSystem;
660
661    case QPlatformTheme::DockWidgetTitleFont:
662        return kCTFontUIFontSmallSystem;
663
664    case QPlatformTheme::PushButtonFont:
665        return kCTFontUIFontPushButton;
666
667    case QPlatformTheme::CheckBoxFont:
668    case QPlatformTheme::RadioButtonFont:
669        return kCTFontUIFontSystem;
670
671    case QPlatformTheme::ToolButtonFont:
672        return kCTFontUIFontSmallToolbar;
673
674    case QPlatformTheme::ItemViewFont:
675        return kCTFontUIFontSystem;
676
677    case QPlatformTheme::ListViewFont:
678        return kCTFontUIFontViews;
679
680    case QPlatformTheme::HeaderViewFont:
681        return kCTFontUIFontSmallSystem;
682
683    case QPlatformTheme::ListBoxFont:
684        return kCTFontUIFontViews;
685
686    case QPlatformTheme::ComboMenuItemFont:
687        return kCTFontUIFontSystem;
688
689    case QPlatformTheme::ComboLineEditFont:
690        return kCTFontUIFontViews;
691
692    case QPlatformTheme::SmallFont:
693        return kCTFontUIFontSmallSystem;
694
695    case QPlatformTheme::MiniFont:
696        return kCTFontUIFontMiniSystem;
697
698    case QPlatformTheme::FixedFont:
699        return kCTFontUIFontUserFixedPitch;
700
701    default:
702        return kCTFontUIFontSystem;
703    }
704}
705
706static CTFontDescriptorRef fontDescriptorFromTheme(QPlatformTheme::Font f)
707{
708#if defined(QT_PLATFORM_UIKIT)
709    // Use Dynamic Type to resolve theme fonts if possible, to get
710    // correct font sizes and style based on user configuration.
711    NSString *textStyle = 0;
712    switch (f) {
713    case QPlatformTheme::TitleBarFont:
714    case QPlatformTheme::HeaderViewFont:
715        textStyle = UIFontTextStyleHeadline;
716        break;
717    case QPlatformTheme::MdiSubWindowTitleFont:
718        textStyle = UIFontTextStyleSubheadline;
719        break;
720    case QPlatformTheme::TipLabelFont:
721    case QPlatformTheme::SmallFont:
722        textStyle = UIFontTextStyleFootnote;
723        break;
724    case QPlatformTheme::MiniFont:
725        textStyle = UIFontTextStyleCaption2;
726        break;
727    case QPlatformTheme::FixedFont:
728        // Fall back to regular code path, as iOS doesn't provide
729        // an appropriate text style for this theme font.
730        break;
731    default:
732        textStyle = UIFontTextStyleBody;
733        break;
734    }
735
736    if (textStyle) {
737        UIFontDescriptor *desc = [UIFontDescriptor preferredFontDescriptorWithTextStyle:textStyle];
738        return static_cast<CTFontDescriptorRef>(CFBridgingRetain(desc));
739    }
740#endif // Q_OS_IOS, Q_OS_TVOS, Q_OS_WATCHOS
741
742    // macOS default case and iOS fallback case
743    return descriptorForFontType(fontTypeFromTheme(f));
744}
745
746const QHash<QPlatformTheme::Font, QFont *> &QCoreTextFontDatabase::themeFonts() const
747{
748    if (m_themeFonts.isEmpty()) {
749        for (long f = QPlatformTheme::SystemFont; f < QPlatformTheme::NFonts; f++) {
750            QPlatformTheme::Font ft = static_cast<QPlatformTheme::Font>(f);
751            m_themeFonts.insert(ft, themeFont(ft));
752        }
753    }
754
755    return m_themeFonts;
756}
757
758QFont *QCoreTextFontDatabase::themeFont(QPlatformTheme::Font f) const
759{
760    CTFontDescriptorRef fontDesc = fontDescriptorFromTheme(f);
761    FontDescription fd;
762    getFontDescription(fontDesc, &fd);
763
764    if (!m_systemFontDescriptors.contains(fontDesc))
765        m_systemFontDescriptors.insert(fontDesc);
766    else
767        CFRelease(fontDesc);
768
769    QFont *font = new QFont(fd.familyName, fd.pointSize, fd.weight, fd.style == QFont::StyleItalic);
770    return font;
771}
772
773QFont QCoreTextFontDatabase::defaultFont() const
774{
775    if (defaultFontName.isEmpty()) {
776        QCFType<CTFontDescriptorRef> systemFont = descriptorForFontType(kCTFontUIFontSystem);
777        defaultFontName = QCFString(CTFontDescriptorCopyAttribute(systemFont, kCTFontFamilyNameAttribute));
778    }
779
780    return QFont(defaultFontName);
781}
782
783bool QCoreTextFontDatabase::fontsAlwaysScalable() const
784{
785    return true;
786}
787
788QList<int> QCoreTextFontDatabase::standardSizes() const
789{
790    QList<int> ret;
791    static const unsigned short standard[] =
792        { 9, 10, 11, 12, 13, 14, 18, 24, 36, 48, 64, 72, 96, 144, 288, 0 };
793    ret.reserve(int(sizeof(standard) / sizeof(standard[0])));
794    const unsigned short *sizes = standard;
795    while (*sizes) ret << *sizes++;
796    return ret;
797}
798
799QT_END_NAMESPACE
800
801