summaryrefslogtreecommitdiffstats
path: root/src/gui/text/coretext
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@qt.io>2020-03-31 12:25:59 +0200
committerTor Arne Vestbø <tor.arne.vestbo@qt.io>2020-05-27 17:51:24 +0200
commitce2bc0e2d5b1d647db1e1726ecd434cc8a55accf (patch)
tree004e90f6cb9a79e76f82d248a3eb8951e6095161 /src/gui/text/coretext
parentcbb9c53704dca4fdaba75e1e1fd354cff058876b (diff)
Move CoreText font engine/database to QtGui
Task-number: QTBUG-83255 Change-Id: Id34ed1759fdebbb1d09e51009f0370736002167c Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'src/gui/text/coretext')
-rw-r--r--src/gui/text/coretext/coretext.pri19
-rw-r--r--src/gui/text/coretext/qcoretextfontdatabase.mm798
-rw-r--r--src/gui/text/coretext/qcoretextfontdatabase_p.h116
-rw-r--r--src/gui/text/coretext/qfontengine_coretext.mm1060
-rw-r--r--src/gui/text/coretext/qfontengine_coretext_p.h159
5 files changed, 2152 insertions, 0 deletions
diff --git a/src/gui/text/coretext/coretext.pri b/src/gui/text/coretext/coretext.pri
new file mode 100644
index 0000000000..31470ae583
--- /dev/null
+++ b/src/gui/text/coretext/coretext.pri
@@ -0,0 +1,19 @@
+HEADERS += $$PWD/qcoretextfontdatabase_p.h $$PWD/qfontengine_coretext_p.h
+OBJECTIVE_SOURCES += $$PWD/qfontengine_coretext.mm $$PWD/qcoretextfontdatabase.mm
+
+LIBS_PRIVATE += \
+ -framework CoreFoundation \
+ -framework CoreGraphics \
+ -framework CoreText \
+ -framework Foundation
+
+macos: \
+ LIBS_PRIVATE += -framework AppKit
+else: \
+ LIBS_PRIVATE += -framework UIKit
+
+CONFIG += watchos_coretext
+
+qtConfig(freetype) {
+ QMAKE_USE_PRIVATE += freetype
+}
diff --git a/src/gui/text/coretext/qcoretextfontdatabase.mm b/src/gui/text/coretext/qcoretextfontdatabase.mm
new file mode 100644
index 0000000000..ba3c2424d0
--- /dev/null
+++ b/src/gui/text/coretext/qcoretextfontdatabase.mm
@@ -0,0 +1,798 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qglobal.h"
+
+#include <sys/param.h>
+
+#if defined(Q_OS_MACOS)
+#import <AppKit/AppKit.h>
+#import <IOKit/graphics/IOGraphicsLib.h>
+#elif defined(QT_PLATFORM_UIKIT)
+#import <UIKit/UIFont.h>
+#endif
+
+#include <QtCore/qelapsedtimer.h>
+
+#include "qcoretextfontdatabase_p.h"
+#include "qfontengine_coretext_p.h"
+#if QT_CONFIG(settings)
+#include <QtCore/QSettings>
+#endif
+#include <QtCore/QtEndian>
+#ifndef QT_NO_FREETYPE
+#include <QtGui/private/qfontengine_ft_p.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+// this could become a list of all languages used for each writing
+// system, instead of using the single most common language.
+static const char *languageForWritingSystem[] = {
+ 0, // Any
+ "en", // Latin
+ "el", // Greek
+ "ru", // Cyrillic
+ "hy", // Armenian
+ "he", // Hebrew
+ "ar", // Arabic
+ "syr", // Syriac
+ "div", // Thaana
+ "hi", // Devanagari
+ "bn", // Bengali
+ "pa", // Gurmukhi
+ "gu", // Gujarati
+ "or", // Oriya
+ "ta", // Tamil
+ "te", // Telugu
+ "kn", // Kannada
+ "ml", // Malayalam
+ "si", // Sinhala
+ "th", // Thai
+ "lo", // Lao
+ "bo", // Tibetan
+ "my", // Myanmar
+ "ka", // Georgian
+ "km", // Khmer
+ "zh-Hans", // SimplifiedChinese
+ "zh-Hant", // TraditionalChinese
+ "ja", // Japanese
+ "ko", // Korean
+ "vi", // Vietnamese
+ 0, // Symbol
+ "sga", // Ogham
+ "non", // Runic
+ "man" // N'Ko
+};
+enum { LanguageCount = sizeof(languageForWritingSystem) / sizeof(const char *) };
+
+QCoreTextFontDatabase::QCoreTextFontDatabase()
+ : m_hasPopulatedAliases(false)
+{
+}
+
+QCoreTextFontDatabase::~QCoreTextFontDatabase()
+{
+ for (CTFontDescriptorRef ref : qAsConst(m_systemFontDescriptors))
+ CFRelease(ref);
+}
+
+void QCoreTextFontDatabase::populateFontDatabase()
+{
+ qCDebug(lcQpaFonts) << "Populating font database...";
+ QElapsedTimer elapsed;
+ if (lcQpaFonts().isDebugEnabled())
+ elapsed.start();
+
+ QCFType<CFArrayRef> familyNames = CTFontManagerCopyAvailableFontFamilyNames();
+ for (NSString *familyName in familyNames.as<const NSArray *>())
+ QPlatformFontDatabase::registerFontFamily(QString::fromNSString(familyName));
+
+ qCDebug(lcQpaFonts) << "Populating available families took" << elapsed.restart() << "ms";
+
+ // Force creating the theme fonts to get the descriptors in m_systemFontDescriptors
+ if (m_themeFonts.isEmpty())
+ (void)themeFonts();
+
+ qCDebug(lcQpaFonts) << "Resolving theme fonts took" << elapsed.restart() << "ms";
+
+ for (CTFontDescriptorRef fontDesc : m_systemFontDescriptors)
+ populateFromDescriptor(fontDesc);
+
+ qCDebug(lcQpaFonts) << "Populating system descriptors took" << elapsed.restart() << "ms";
+
+ Q_ASSERT(!m_hasPopulatedAliases);
+}
+
+bool QCoreTextFontDatabase::populateFamilyAliases(const QString &missingFamily)
+{
+#if defined(Q_OS_MACOS)
+ if (m_hasPopulatedAliases)
+ return false;
+
+ // There's no API to go from a localized family name to its non-localized
+ // name, so we have to resort to enumerating all the available fonts and
+ // doing a reverse lookup.
+
+ qCDebug(lcQpaFonts) << "Populating family aliases...";
+ QElapsedTimer elapsed;
+ elapsed.start();
+
+ QString nonLocalizedMatch;
+ QCFType<CFArrayRef> familyNames = CTFontManagerCopyAvailableFontFamilyNames();
+ NSFontManager *fontManager = NSFontManager.sharedFontManager;
+ for (NSString *familyName in familyNames.as<const NSArray *>()) {
+ NSString *localizedFamilyName = [fontManager localizedNameForFamily:familyName face:nil];
+ if (![localizedFamilyName isEqual:familyName]) {
+ QString nonLocalizedFamily = QString::fromNSString(familyName);
+ QString localizedFamily = QString::fromNSString(localizedFamilyName);
+ QPlatformFontDatabase::registerAliasToFontFamily(nonLocalizedFamily, localizedFamily);
+ if (localizedFamily == missingFamily)
+ nonLocalizedMatch = nonLocalizedFamily;
+ }
+ }
+ m_hasPopulatedAliases = true;
+
+ if (lcQpaFonts().isWarningEnabled()) {
+ QString warningMessage;
+ QDebug msg(&warningMessage);
+
+ msg << "Populating font family aliases took" << elapsed.restart() << "ms.";
+ if (!nonLocalizedMatch.isNull())
+ msg << "Replace uses of" << missingFamily << "with its non-localized name" << nonLocalizedMatch;
+ else
+ msg << "Replace uses of missing font family" << missingFamily << "with one that exists";
+ msg << "to avoid this cost.";
+
+ qCWarning(lcQpaFonts) << qPrintable(warningMessage);
+ }
+
+ return true;
+#else
+ Q_UNUSED(missingFamily);
+ return false;
+#endif
+}
+
+void QCoreTextFontDatabase::populateFamily(const QString &familyName)
+{
+ QCFType<CFMutableDictionaryRef> attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, QCFString(familyName));
+ QCFType<CTFontDescriptorRef> nameOnlyDescriptor = CTFontDescriptorCreateWithAttributes(attributes);
+
+ // A single family might match several different fonts with different styles eg.
+ QCFType<CFArrayRef> matchingFonts = (CFArrayRef) CTFontDescriptorCreateMatchingFontDescriptors(nameOnlyDescriptor, 0);
+ if (!matchingFonts) {
+ qCWarning(lcQpaFonts) << "QCoreTextFontDatabase: Found no matching fonts for family" << familyName;
+ return;
+ }
+
+ const int numFonts = CFArrayGetCount(matchingFonts);
+ for (int i = 0; i < numFonts; ++i)
+ populateFromDescriptor(CTFontDescriptorRef(CFArrayGetValueAtIndex(matchingFonts, i)), familyName);
+}
+
+void QCoreTextFontDatabase::invalidate()
+{
+ m_hasPopulatedAliases = false;
+}
+
+struct FontDescription {
+ QCFString familyName;
+ QCFString styleName;
+ QString foundryName;
+ QFont::Weight weight;
+ QFont::Style style;
+ QFont::Stretch stretch;
+ qreal pointSize;
+ bool fixedPitch;
+ QSupportedWritingSystems writingSystems;
+};
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_DECL_UNUSED static inline QDebug operator<<(QDebug debug, const FontDescription &fd)
+{
+ QDebugStateSaver saver(debug);
+ return debug.nospace() << "FontDescription("
+ << "familyName=" << QString(fd.familyName)
+ << ", styleName=" << QString(fd.styleName)
+ << ", foundry=" << fd.foundryName
+ << ", weight=" << fd.weight
+ << ", style=" << fd.style
+ << ", stretch=" << fd.stretch
+ << ", pointSize=" << fd.pointSize
+ << ", fixedPitch=" << fd.fixedPitch
+ << ", writingSystems=" << fd.writingSystems
+ << ")";
+}
+#endif
+
+static void getFontDescription(CTFontDescriptorRef font, FontDescription *fd)
+{
+ QCFType<CFDictionaryRef> styles = (CFDictionaryRef) CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute);
+
+ fd->foundryName = QStringLiteral("CoreText");
+ fd->familyName = (CFStringRef) CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute);
+ fd->styleName = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute);
+ fd->weight = QFont::Normal;
+ fd->style = QFont::StyleNormal;
+ fd->stretch = QFont::Unstretched;
+ fd->fixedPitch = false;
+
+ if (QCFType<CTFontRef> tempFont = CTFontCreateWithFontDescriptor(font, 0.0, 0)) {
+ uint tag = MAKE_TAG('O', 'S', '/', '2');
+ CTFontRef tempFontRef = tempFont;
+ void *userData = reinterpret_cast<void *>(&tempFontRef);
+ uint length = 128;
+ QVarLengthArray<uchar, 128> os2Table(length);
+ if (QCoreTextFontEngine::ct_getSfntTable(userData, tag, os2Table.data(), &length) && length >= 86) {
+ if (length > uint(os2Table.length())) {
+ os2Table.resize(length);
+ if (!QCoreTextFontEngine::ct_getSfntTable(userData, tag, os2Table.data(), &length))
+ Q_UNREACHABLE();
+ Q_ASSERT(length >= 86);
+ }
+ fd->writingSystems = QPlatformFontDatabase::writingSystemsFromOS2Table(reinterpret_cast<const char *>(os2Table.data()), length);
+ }
+ }
+
+ if (styles) {
+ if (CFNumberRef weightValue = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontWeightTrait)) {
+ double normalizedWeight;
+ if (CFNumberGetValue(weightValue, kCFNumberFloat64Type, &normalizedWeight))
+ fd->weight = QCoreTextFontEngine::qtWeightFromCFWeight(float(normalizedWeight));
+ }
+ if (CFNumberRef italic = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontSlantTrait)) {
+ double d;
+ if (CFNumberGetValue(italic, kCFNumberDoubleType, &d)) {
+ if (d > 0.0)
+ fd->style = QFont::StyleItalic;
+ }
+ }
+ if (CFNumberRef symbolic = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontSymbolicTrait)) {
+ int d;
+ if (CFNumberGetValue(symbolic, kCFNumberSInt32Type, &d)) {
+ if (d & kCTFontMonoSpaceTrait)
+ fd->fixedPitch = true;
+ if (d & kCTFontExpandedTrait)
+ fd->stretch = QFont::Expanded;
+ else if (d & kCTFontCondensedTrait)
+ fd->stretch = QFont::Condensed;
+ }
+ }
+ }
+
+ if (QCFType<CFNumberRef> size = (CFNumberRef) CTFontDescriptorCopyAttribute(font, kCTFontSizeAttribute)) {
+ if (CFNumberIsFloatType(size)) {
+ double d;
+ CFNumberGetValue(size, kCFNumberDoubleType, &d);
+ fd->pointSize = d;
+ } else {
+ int i;
+ CFNumberGetValue(size, kCFNumberIntType, &i);
+ fd->pointSize = i;
+ }
+ }
+
+ if (QCFType<CFArrayRef> languages = (CFArrayRef) CTFontDescriptorCopyAttribute(font, kCTFontLanguagesAttribute)) {
+ CFIndex length = CFArrayGetCount(languages);
+ for (int i = 1; i < LanguageCount; ++i) {
+ if (!languageForWritingSystem[i])
+ continue;
+ QCFString lang = CFStringCreateWithCString(NULL, languageForWritingSystem[i], kCFStringEncodingASCII);
+ if (CFArrayContainsValue(languages, CFRangeMake(0, length), lang))
+ fd->writingSystems.setSupported(QFontDatabase::WritingSystem(i));
+ }
+ }
+}
+
+void QCoreTextFontDatabase::populateFromDescriptor(CTFontDescriptorRef font, const QString &familyName, QFontDatabasePrivate::ApplicationFont *applicationFont)
+{
+ FontDescription fd;
+ getFontDescription(font, &fd);
+
+ // Note: The familyName we are registering, and the family name of the font descriptor, may not
+ // match, as CTFontDescriptorCreateMatchingFontDescriptors will return descriptors for replacement
+ // fonts if a font family does not have any fonts available on the system.
+ QString family = !familyName.isNull() ? familyName : static_cast<QString>(fd.familyName);
+
+ if (applicationFont != nullptr) {
+ QFontDatabasePrivate::ApplicationFont::Properties properties;
+ properties.familyName = family;
+ properties.styleName = fd.styleName;
+ properties.weight = fd.weight;
+ properties.stretch = fd.stretch;
+ properties.style = fd.style;
+
+ applicationFont->properties.append(properties);
+ }
+
+ CFRetain(font);
+ QPlatformFontDatabase::registerFont(family, fd.styleName, fd.foundryName, fd.weight, fd.style, fd.stretch,
+ true /* antialiased */, true /* scalable */, 0 /* pixelSize, ignored as font is scalable */,
+ fd.fixedPitch, fd.writingSystems, (void *)font);
+}
+
+static NSString * const kQtFontDataAttribute = @"QtFontDataAttribute";
+
+template <typename T>
+T *descriptorAttribute(CTFontDescriptorRef descriptor, CFStringRef name)
+{
+ return [static_cast<T *>(CTFontDescriptorCopyAttribute(descriptor, name)) autorelease];
+}
+
+void QCoreTextFontDatabase::releaseHandle(void *handle)
+{
+ CTFontDescriptorRef descriptor = static_cast<CTFontDescriptorRef>(handle);
+ if (NSValue *fontDataValue = descriptorAttribute<NSValue>(descriptor, (CFStringRef)kQtFontDataAttribute)) {
+ QByteArray *fontData = static_cast<QByteArray *>(fontDataValue.pointerValue);
+ delete fontData;
+ }
+ CFRelease(descriptor);
+}
+
+extern CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef);
+
+template <>
+QFontEngine *QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>::fontEngine(const QFontDef &fontDef, void *usrPtr)
+{
+ QCFType<CTFontDescriptorRef> descriptor = QCFType<CTFontDescriptorRef>::constructFromGet(
+ static_cast<CTFontDescriptorRef>(usrPtr));
+
+ // Since we do not pass in the destination DPI to CoreText when making
+ // the font, we need to pass in a point size which is scaled to include
+ // the DPI. The default DPI for the screen is 72, thus the scale factor
+ // is destinationDpi / 72, but since pixelSize = pointSize / 72 * dpi,
+ // the pixelSize is actually the scaled point size for the destination
+ // DPI, and we can use that directly.
+ qreal scaledPointSize = fontDef.pixelSize;
+
+ CGAffineTransform matrix = qt_transform_from_fontdef(fontDef);
+ if (QCFType<CTFontRef> font = CTFontCreateWithFontDescriptor(descriptor, scaledPointSize, &matrix))
+ return new QCoreTextFontEngine(font, fontDef);
+
+ return nullptr;
+}
+
+#ifndef QT_NO_FREETYPE
+template <>
+QFontEngine *QCoreTextFontDatabaseEngineFactory<QFontEngineFT>::fontEngine(const QFontDef &fontDef, void *usrPtr)
+{
+ CTFontDescriptorRef descriptor = static_cast<CTFontDescriptorRef>(usrPtr);
+
+ if (NSValue *fontDataValue = descriptorAttribute<NSValue>(descriptor, (CFStringRef)kQtFontDataAttribute)) {
+ QByteArray *fontData = static_cast<QByteArray *>(fontDataValue.pointerValue);
+ return QFontEngineFT::create(*fontData, fontDef.pixelSize,
+ static_cast<QFont::HintingPreference>(fontDef.hintingPreference));
+ } else if (NSURL *url = descriptorAttribute<NSURL>(descriptor, kCTFontURLAttribute)) {
+ Q_ASSERT(url.fileURL);
+ QFontEngine::FaceId faceId;
+ faceId.filename = QString::fromNSString(url.path).toUtf8();
+ return QFontEngineFT::create(fontDef, faceId);
+ }
+ Q_UNREACHABLE();
+}
+#endif
+
+template <class T>
+QFontEngine *QCoreTextFontDatabaseEngineFactory<T>::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
+{
+ return T::create(fontData, pixelSize, hintingPreference);
+}
+
+// Explicitly instantiate so that we don't need the plugin to involve FreeType
+template class QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>;
+#ifndef QT_NO_FREETYPE
+template class QCoreTextFontDatabaseEngineFactory<QFontEngineFT>;
+#endif
+
+CTFontDescriptorRef descriptorForFamily(const QString &familyName)
+{
+ return CTFontDescriptorCreateWithAttributes(CFDictionaryRef(@{
+ (id)kCTFontFamilyNameAttribute: familyName.toNSString()
+ }));
+}
+
+CTFontDescriptorRef descriptorForFamily(const char *familyName)
+{
+ return descriptorForFamily(QString::fromLatin1(familyName));
+}
+
+CFArrayRef fallbacksForDescriptor(CTFontDescriptorRef descriptor)
+{
+ QCFType<CTFontRef> font = CTFontCreateWithFontDescriptor(descriptor, 0.0, nullptr);
+ if (!font) {
+ qCWarning(lcQpaFonts) << "Failed to create fallback font for" << descriptor;
+ return nullptr;
+ }
+
+ CFArrayRef cascadeList = CFArrayRef(CTFontCopyDefaultCascadeListForLanguages(font,
+ (CFArrayRef)[NSUserDefaults.standardUserDefaults stringArrayForKey:@"AppleLanguages"]));
+
+ if (!cascadeList) {
+ qCWarning(lcQpaFonts) << "Failed to create fallback cascade list for" << descriptor;
+ return nullptr;
+ }
+
+ return cascadeList;
+}
+
+CFArrayRef QCoreTextFontDatabase::fallbacksForFamily(const QString &family)
+{
+ if (family.isEmpty())
+ return nullptr;
+
+ QCFType<CTFontDescriptorRef> fontDescriptor = descriptorForFamily(family);
+ if (!fontDescriptor) {
+ qCWarning(lcQpaFonts) << "Failed to create fallback font descriptor for" << family;
+ return nullptr;
+ }
+
+ // If the font is not available we want to fall back to the style hint.
+ // By creating a matching font descriptor we can verify whether the font
+ // is available or not, and avoid CTFontCreateWithFontDescriptor picking
+ // a default font for us based on incomplete information.
+ fontDescriptor = CTFontDescriptorCreateMatchingFontDescriptor(fontDescriptor, 0);
+ if (!fontDescriptor)
+ return nullptr;
+
+ return fallbacksForDescriptor(fontDescriptor);
+}
+
+CTFontDescriptorRef descriptorForFontType(CTFontUIFontType uiType)
+{
+ static const CGFloat kDefaultSizeForRequestedUIType = 0.0;
+ QCFType<CTFontRef> ctFont = CTFontCreateUIFontForLanguage(
+ uiType, kDefaultSizeForRequestedUIType, nullptr);
+ return CTFontCopyFontDescriptor(ctFont);
+}
+
+CTFontDescriptorRef descriptorForStyle(QFont::StyleHint styleHint)
+{
+ switch (styleHint) {
+ case QFont::SansSerif: return descriptorForFamily("Helvetica");
+ case QFont::Serif: return descriptorForFamily("Times New Roman");
+ case QFont::Monospace: return descriptorForFamily("Menlo");
+#ifdef Q_OS_MACOS
+ case QFont::Cursive: return descriptorForFamily("Apple Chancery");
+#endif
+ case QFont::Fantasy: return descriptorForFamily("Zapfino");
+ case QFont::TypeWriter: return descriptorForFamily("American Typewriter");
+ case QFont::AnyStyle: Q_FALLTHROUGH();
+ case QFont::System: return descriptorForFontType(kCTFontUIFontSystem);
+ default: return nullptr; // No matching font on this platform
+ }
+}
+
+QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
+{
+ Q_UNUSED(style);
+
+ qCDebug(lcQpaFonts).nospace() << "Resolving fallbacks families for"
+ << (!family.isEmpty() ? qPrintable(QLatin1String(" family '%1' with").arg(family)) : "")
+ << " style hint " << styleHint;
+
+ QMacAutoReleasePool pool;
+
+ QStringList fallbackList;
+
+ QCFType<CFArrayRef> fallbackFonts = fallbacksForFamily(family);
+ if (!fallbackFonts || !CFArrayGetCount(fallbackFonts)) {
+ // We were not able to find a fallback for the specific family,
+ // or the family was empty, so we fall back to the style hint.
+ if (!family.isEmpty())
+ qCDebug(lcQpaFonts) << "No fallbacks found. Using style hint instead";
+
+ if (QCFType<CTFontDescriptorRef> styleDescriptor = descriptorForStyle(styleHint)) {
+ CFMutableArrayRef tmp = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+ CFArrayAppendValue(tmp, styleDescriptor);
+ QCFType<CFArrayRef> styleFallbacks = fallbacksForDescriptor(styleDescriptor);
+ CFArrayAppendArray(tmp, styleFallbacks, CFRangeMake(0, CFArrayGetCount(styleFallbacks)));
+ fallbackFonts = tmp;
+ }
+ }
+
+ if (!fallbackFonts)
+ return fallbackList;
+
+ const int numberOfFallbacks = CFArrayGetCount(fallbackFonts);
+ for (int i = 0; i < numberOfFallbacks; ++i) {
+ auto fallbackDescriptor = CTFontDescriptorRef(CFArrayGetValueAtIndex(fallbackFonts, i));
+ auto fallbackFamilyName = QCFString(CTFontDescriptorCopyAttribute(fallbackDescriptor, kCTFontFamilyNameAttribute));
+
+ if (!isFamilyPopulated(fallbackFamilyName)) {
+ // We need to populate, or at least register the fallback fonts,
+ // otherwise the Qt font database may not know they exist.
+ if (isPrivateFontFamily(fallbackFamilyName))
+ const_cast<QCoreTextFontDatabase *>(this)->populateFromDescriptor(fallbackDescriptor);
+ else
+ registerFontFamily(fallbackFamilyName);
+ }
+
+ fallbackList.append(fallbackFamilyName);
+ }
+
+ // Some fallback fonts will have have an order in the list returned
+ // by Core Text that would indicate they should be preferred for e.g.
+ // Arabic, or Emoji, while in reality only supporting a tiny subset
+ // of the required glyphs, or representing them by question marks.
+ // Move these to the end, so that the proper fonts are preferred.
+ for (const char *family : { ".Apple Symbols Fallback", ".Noto Sans Universal" }) {
+ int index = fallbackList.indexOf(QLatin1String(family));
+ if (index >= 0)
+ fallbackList.move(index, fallbackList.size() - 1);
+ }
+
+#if defined(Q_OS_MACOS)
+ // Since we are only returning a list of default fonts for the current language, we do not
+ // cover all Unicode completely. This was especially an issue for some of the common script
+ // symbols such as mathematical symbols, currency or geometric shapes. To minimize the risk
+ // of missing glyphs, we add Arial Unicode MS as a final fail safe, since this covers most
+ // of Unicode 2.1.
+ if (!fallbackList.contains(QStringLiteral("Arial Unicode MS")))
+ fallbackList.append(QStringLiteral("Arial Unicode MS"));
+ // Since some symbols (specifically Braille) are not in Arial Unicode MS, we
+ // add Apple Symbols to cover those too.
+ if (!fallbackList.contains(QStringLiteral("Apple Symbols")))
+ fallbackList.append(QStringLiteral("Apple Symbols"));
+#endif
+
+ extern QStringList qt_sort_families_by_writing_system(QChar::Script, const QStringList &);
+ fallbackList = qt_sort_families_by_writing_system(script, fallbackList);
+
+ qCDebug(lcQpaFonts).nospace() << "Fallback families ordered by script " << script << ": " << fallbackList;
+
+ return fallbackList;
+}
+
+QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont)
+{
+ QCFType<CFArrayRef> fonts;
+
+ if (!fontData.isEmpty()) {
+ QCFType<CFDataRef> fontDataReference = fontData.toRawCFData();
+ if (QCFType<CTFontDescriptorRef> descriptor = CTFontManagerCreateFontDescriptorFromData(fontDataReference)) {
+ // There's no way to get the data back out of a font descriptor created with
+ // CTFontManagerCreateFontDescriptorFromData, so we attach the data manually.
+ NSDictionary *attributes = @{ kQtFontDataAttribute : [NSValue valueWithPointer:new QByteArray(fontData)] };
+ descriptor = CTFontDescriptorCreateCopyWithAttributes(descriptor, (CFDictionaryRef)attributes);
+ CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+ CFArrayAppendValue(array, descriptor);
+ fonts = array;
+ }
+ } else {
+ QCFType<CFURLRef> fontURL = QUrl::fromLocalFile(fileName).toCFURL();
+ fonts = CTFontManagerCreateFontDescriptorsFromURL(fontURL);
+ }
+
+ if (!fonts)
+ return QStringList();
+
+ QStringList families;
+ const int numFonts = CFArrayGetCount(fonts);
+ for (int i = 0; i < numFonts; ++i) {
+ CTFontDescriptorRef fontDescriptor = CTFontDescriptorRef(CFArrayGetValueAtIndex(fonts, i));
+ populateFromDescriptor(fontDescriptor, QString(), applicationFont);
+ QCFType<CFStringRef> familyName = CFStringRef(CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontFamilyNameAttribute));
+ families.append(QString::fromCFString(familyName));
+ }
+
+ // Note: We don't do font matching via CoreText for application fonts, so we don't
+ // need to enable font matching for them via CTFontManagerEnableFontDescriptors.
+
+ return families;
+}
+
+bool QCoreTextFontDatabase::isPrivateFontFamily(const QString &family) const
+{
+ if (family.startsWith(QLatin1Char('.')) || family == QLatin1String("LastResort"))
+ return true;
+
+ return QPlatformFontDatabase::isPrivateFontFamily(family);
+}
+
+static CTFontUIFontType fontTypeFromTheme(QPlatformTheme::Font f)
+{
+ switch (f) {
+ case QPlatformTheme::SystemFont:
+ return kCTFontUIFontSystem;
+
+ case QPlatformTheme::MenuFont:
+ case QPlatformTheme::MenuBarFont:
+ case QPlatformTheme::MenuItemFont:
+ return kCTFontUIFontMenuItem;
+
+ case QPlatformTheme::MessageBoxFont:
+ return kCTFontUIFontEmphasizedSystem;
+
+ case QPlatformTheme::LabelFont:
+ return kCTFontUIFontSystem;
+
+ case QPlatformTheme::TipLabelFont:
+ return kCTFontUIFontToolTip;
+
+ case QPlatformTheme::StatusBarFont:
+ return kCTFontUIFontSystem;
+
+ case QPlatformTheme::TitleBarFont:
+ return kCTFontUIFontWindowTitle;
+
+ case QPlatformTheme::MdiSubWindowTitleFont:
+ return kCTFontUIFontSystem;
+
+ case QPlatformTheme::DockWidgetTitleFont:
+ return kCTFontUIFontSmallSystem;
+
+ case QPlatformTheme::PushButtonFont:
+ return kCTFontUIFontPushButton;
+
+ case QPlatformTheme::CheckBoxFont:
+ case QPlatformTheme::RadioButtonFont:
+ return kCTFontUIFontSystem;
+
+ case QPlatformTheme::ToolButtonFont:
+ return kCTFontUIFontSmallToolbar;
+
+ case QPlatformTheme::ItemViewFont:
+ return kCTFontUIFontSystem;
+
+ case QPlatformTheme::ListViewFont:
+ return kCTFontUIFontViews;
+
+ case QPlatformTheme::HeaderViewFont:
+ return kCTFontUIFontSmallSystem;
+
+ case QPlatformTheme::ListBoxFont:
+ return kCTFontUIFontViews;
+
+ case QPlatformTheme::ComboMenuItemFont:
+ return kCTFontUIFontSystem;
+
+ case QPlatformTheme::ComboLineEditFont:
+ return kCTFontUIFontViews;
+
+ case QPlatformTheme::SmallFont:
+ return kCTFontUIFontSmallSystem;
+
+ case QPlatformTheme::MiniFont:
+ return kCTFontUIFontMiniSystem;
+
+ case QPlatformTheme::FixedFont:
+ return kCTFontUIFontUserFixedPitch;
+
+ default:
+ return kCTFontUIFontSystem;
+ }
+}
+
+static CTFontDescriptorRef fontDescriptorFromTheme(QPlatformTheme::Font f)
+{
+#if defined(QT_PLATFORM_UIKIT)
+ // Use Dynamic Type to resolve theme fonts if possible, to get
+ // correct font sizes and style based on user configuration.
+ NSString *textStyle = 0;
+ switch (f) {
+ case QPlatformTheme::TitleBarFont:
+ case QPlatformTheme::HeaderViewFont:
+ textStyle = UIFontTextStyleHeadline;
+ break;
+ case QPlatformTheme::MdiSubWindowTitleFont:
+ textStyle = UIFontTextStyleSubheadline;
+ break;
+ case QPlatformTheme::TipLabelFont:
+ case QPlatformTheme::SmallFont:
+ textStyle = UIFontTextStyleFootnote;
+ break;
+ case QPlatformTheme::MiniFont:
+ textStyle = UIFontTextStyleCaption2;
+ break;
+ case QPlatformTheme::FixedFont:
+ // Fall back to regular code path, as iOS doesn't provide
+ // an appropriate text style for this theme font.
+ break;
+ default:
+ textStyle = UIFontTextStyleBody;
+ break;
+ }
+
+ if (textStyle) {
+ UIFontDescriptor *desc = [UIFontDescriptor preferredFontDescriptorWithTextStyle:textStyle];
+ return static_cast<CTFontDescriptorRef>(CFBridgingRetain(desc));
+ }
+#endif // Q_OS_IOS, Q_OS_TVOS, Q_OS_WATCHOS
+
+ // macOS default case and iOS fallback case
+ return descriptorForFontType(fontTypeFromTheme(f));
+}
+
+const QHash<QPlatformTheme::Font, QFont *> &QCoreTextFontDatabase::themeFonts() const
+{
+ if (m_themeFonts.isEmpty()) {
+ for (long f = QPlatformTheme::SystemFont; f < QPlatformTheme::NFonts; f++) {
+ QPlatformTheme::Font ft = static_cast<QPlatformTheme::Font>(f);
+ m_themeFonts.insert(ft, themeFont(ft));
+ }
+ }
+
+ return m_themeFonts;
+}
+
+QFont *QCoreTextFontDatabase::themeFont(QPlatformTheme::Font f) const
+{
+ CTFontDescriptorRef fontDesc = fontDescriptorFromTheme(f);
+ FontDescription fd;
+ getFontDescription(fontDesc, &fd);
+
+ if (!m_systemFontDescriptors.contains(fontDesc))
+ m_systemFontDescriptors.insert(fontDesc);
+ else
+ CFRelease(fontDesc);
+
+ QFont *font = new QFont(fd.familyName, fd.pointSize, fd.weight, fd.style == QFont::StyleItalic);
+ return font;
+}
+
+QFont QCoreTextFontDatabase::defaultFont() const
+{
+ if (defaultFontName.isEmpty()) {
+ QCFType<CTFontDescriptorRef> systemFont = descriptorForFontType(kCTFontUIFontSystem);
+ defaultFontName = QCFString(CTFontDescriptorCopyAttribute(systemFont, kCTFontFamilyNameAttribute));
+ }
+
+ return QFont(defaultFontName);
+}
+
+bool QCoreTextFontDatabase::fontsAlwaysScalable() const
+{
+ return true;
+}
+
+QList<int> QCoreTextFontDatabase::standardSizes() const
+{
+ QList<int> ret;
+ static const unsigned short standard[] =
+ { 9, 10, 11, 12, 13, 14, 18, 24, 36, 48, 64, 72, 96, 144, 288, 0 };
+ ret.reserve(int(sizeof(standard) / sizeof(standard[0])));
+ const unsigned short *sizes = standard;
+ while (*sizes) ret << *sizes++;
+ return ret;
+}
+
+QT_END_NAMESPACE
+
diff --git a/src/gui/text/coretext/qcoretextfontdatabase_p.h b/src/gui/text/coretext/qcoretextfontdatabase_p.h
new file mode 100644
index 0000000000..971ac3cfde
--- /dev/null
+++ b/src/gui/text/coretext/qcoretextfontdatabase_p.h
@@ -0,0 +1,116 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QCORETEXTFONTDATABASE_H
+#define QCORETEXTFONTDATABASE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qglobal.h>
+
+#include <qpa/qplatformfontdatabase.h>
+#include <qpa/qplatformtheme.h>
+#include <private/qcore_mac_p.h>
+
+Q_FORWARD_DECLARE_CF_TYPE(CTFontDescriptor);
+Q_FORWARD_DECLARE_CF_TYPE(CTFont);
+
+Q_DECLARE_METATYPE(QCFType<CGFontRef>);
+Q_DECLARE_METATYPE(QCFType<CFURLRef>);
+
+QT_BEGIN_NAMESPACE
+
+class Q_GUI_EXPORT QCoreTextFontDatabase : public QPlatformFontDatabase
+{
+public:
+ QCoreTextFontDatabase();
+ ~QCoreTextFontDatabase();
+ void populateFontDatabase() override;
+ bool populateFamilyAliases(const QString &missingFamily) override;
+ void populateFamily(const QString &familyName) override;
+ void invalidate() override;
+
+ QStringList fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const override;
+ QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr) override;
+ void releaseHandle(void *handle) override;
+ bool isPrivateFontFamily(const QString &family) const override;
+ QFont defaultFont() const override;
+ bool fontsAlwaysScalable() const override;
+ QList<int> standardSizes() const override;
+
+ // For iOS and OS X platform themes
+ QFont *themeFont(QPlatformTheme::Font) const;
+ const QHash<QPlatformTheme::Font, QFont *> &themeFonts() const;
+
+protected:
+ mutable QSet<CTFontDescriptorRef> m_systemFontDescriptors;
+
+private:
+ void populateFromDescriptor(CTFontDescriptorRef font, const QString &familyName = QString(), QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr);
+ static CFArrayRef fallbacksForFamily(const QString &family);
+
+ mutable QString defaultFontName;
+
+ mutable QHash<QPlatformTheme::Font, QFont *> m_themeFonts;
+ bool m_hasPopulatedAliases;
+};
+
+// Split out into separate template class so that the compiler doesn't have
+// to generate code for each override in QCoreTextFontDatabase for each T.
+
+template <class T>
+class Q_GUI_EXPORT QCoreTextFontDatabaseEngineFactory : public QCoreTextFontDatabase
+{
+public:
+ QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
+ QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) override;
+};
+
+QT_END_NAMESPACE
+
+#endif // QCORETEXTFONTDATABASE_H
diff --git a/src/gui/text/coretext/qfontengine_coretext.mm b/src/gui/text/coretext/qfontengine_coretext.mm
new file mode 100644
index 0000000000..57fbf6032e
--- /dev/null
+++ b/src/gui/text/coretext/qfontengine_coretext.mm
@@ -0,0 +1,1060 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#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 <cmath>
+
+#if defined(Q_OS_MACOS)
+#import <AppKit/AppKit.h>
+#endif
+
+#if defined(QT_PLATFORM_UIKIT)
+#import <UIKit/UIKit.h>
+#endif
+
+// 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
+
+Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts")
+
+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)
+{
+ Q_UNUSED(hintingPreference);
+
+ 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();
+
+ QCFString family = CTFontCopyFamilyName(ctfont);
+ fontDef.family = 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);
+ fontDef.weight = QCoreTextFontEngine::qtWeightFromCFWeight(getTraitValue(allTraits, kCTFontWeightTrait));
+ 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))
+ synthesisFlags |= SynthesizedBold;
+ // XXX: we probably don't need to synthesis italic for oblique font
+ if (fontDef.style != QFont::StyleNormal && !(traits & kCTFontItalicTrait))
+ synthesisFlags |= SynthesizedItalic;
+
+ avgCharWidth = 0;
+ QByteArray os2Table = getSfntTable(MAKE_TAG('O', 'S', '/', '2'));
+ 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;
+}
+
+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];
+}
+
+bool 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 false;
+ }
+
+ QVarLengthArray<CGGlyph> cgGlyphs(len);
+ CTFontGetGlyphsForCharacters(ctfont, (const UniChar*)str, cgGlyphs.data(), len);
+
+ int glyph_pos = 0;
+ for (int i = 0; i < len; ++i) {
+ glyphs->glyphs[glyph_pos] = cgGlyphs[i];
+ if (glyph_pos < i)
+ cgGlyphs[glyph_pos] = cgGlyphs[i];
+ glyph_pos++;
+
+ // If it's a non-BMP char, skip the lower part of surrogate pair and go
+ // directly to the next char without increasing glyph_pos
+ if (str[i].isHighSurrogate() && i < len-1 && str[i+1].isLowSurrogate())
+ ++i;
+ }
+
+ *nglyphs = glyph_pos;
+ glyphs->numGlyphs = glyph_pos;
+
+ if (!(flags & GlyphIndicesOnly))
+ loadAdvancesForGlyphs(cgGlyphs, glyphs);
+
+ return true;
+}
+
+glyph_metrics_t QCoreTextFontEngine::boundingBox(const QGlyphLayout &glyphs)
+{
+ QFixed w;
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+ bool round = fontDef.styleStrategy & QFont::ForceIntegerMetrics;
+QT_WARNING_POP
+
+ for (int i = 0; i < glyphs.numGlyphs; ++i) {
+ w += round ? glyphs.effectiveAdvance(i).round()
+ : glyphs.effectiveAdvance(i);
+ }
+ return glyph_metrics_t(0, -(ascent()), w - lastRightBearing(glyphs, round), ascent()+descent(), w, 0);
+}
+
+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);
+
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+ if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) {
+QT_WARNING_POP
+ ret.xoff = ret.xoff.round();
+ ret.yoff = ret.yoff.round();
+ }
+
+ return ret;
+}
+
+void QCoreTextFontEngine::initializeHeightMetrics() const
+{
+ m_ascent = QFixed::fromReal(CTFontGetAscent(ctfont));
+ m_descent = QFixed::fromReal(CTFontGetDescent(ctfont));
+ m_leading = QFixed::fromReal(CTFontGetLeading(ctfont));
+
+ QFontEngine::initializeHeightMetrics();
+}
+
+QFixed QCoreTextFontEngine::capHeight() const
+{
+ QFixed c = QFixed::fromReal(CTFontGetCapHeight(ctfont));
+ if (c <= 0)
+ return calculatedCapHeight();
+
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+ if (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
+QT_WARNING_POP
+ c = c.round();
+
+ return c;
+}
+
+QFixed QCoreTextFontEngine::xHeight() const
+{
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+ return (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
+ ? QFixed::fromReal(CTFontGetXHeight(ctfont)).round()
+ : QFixed::fromReal(CTFontGetXHeight(ctfont));
+QT_WARNING_POP
+}
+
+QFixed QCoreTextFontEngine::averageCharWidth() const
+{
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+ return (fontDef.styleStrategy & QFont::ForceIntegerMetrics)
+ ? avgCharWidth.round() : avgCharWidth;
+QT_WARNING_POP
+}
+
+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;
+}
+
+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);
+
+ 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) {
+ CGContextSetTextPosition(ctx, positions[0].x.toReal() + 0.5 * lineThickness().toReal(),
+ 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, QFixed subPixelPosition, const QTransform &matrix, GlyphFormat format)
+{
+ if (matrix.type() > QTransform::TxScale)
+ return QFontEngine::alphaMapBoundingBox(glyph, subPixelPosition, matrix, format);
+
+ glyph_metrics_t br = boundingBox(glyph);
+ qcoretextfontengine_scaleMetrics(br, matrix);
+
+ // 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) {
+ // Drawing a glyph at x-position 0 with anti-aliasing enabled
+ // 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() const
+{
+ return shouldSmoothFont() && fontSmoothing() == Subpixel;
+}
+
+qreal QCoreTextFontEngine::fontSmoothingGamma()
+{
+ return 2.0;
+}
+
+QImage QCoreTextFontEngine::imageForGlyph(glyph_t glyph, QFixed 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;
+
+ QCFType<CGColorSpaceRef> colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
+ QCFType<CGContextRef> ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(),
+ 8, im.bytesPerLine(), colorspace,
+ qt_mac_bitmapInfoForImage(im));
+ 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 (!qt_mac_applicationIsInDarkMode())
+ 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.toReal();
+ qreal pos_y = im.height() + br.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) {
+ CGContextSetTextPosition(ctx, pos_x + 0.5 * lineThickness().toReal(), 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, QFixed subPixelPosition)
+{
+ return alphaMapForGlyph(glyph, subPixelPosition, QTransform());
+}
+
+QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph, QFixed 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, QFixed 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, QFixed 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);
+
+ for (int i = 0; i < numGlyphs; ++i) {
+ QFixed advance = QFixed::fromReal(advances[i].width);
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+ glyphs->advances[i] = fontDef.styleStrategy & QFont::ForceIntegerMetrics
+ ? advance.round() : advance;
+QT_WARNING_POP
+ }
+}
+
+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);
+
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+ if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) {
+QT_WARNING_POP
+ result.ascent = result.ascent.round();
+ result.descent = result.descent.round();
+ result.leading = result.leading.round();
+ result.italicAngle = result.italicAngle.round();
+ result.capHeight = result.capHeight.round();
+ result.lineWidth = result.lineWidth.round();
+ }
+
+ 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);
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/text/coretext/qfontengine_coretext_p.h b/src/gui/text/coretext/qfontengine_coretext_p.h
new file mode 100644
index 0000000000..fe8161ee8d
--- /dev/null
+++ b/src/gui/text/coretext/qfontengine_coretext_p.h
@@ -0,0 +1,159 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QFONTENGINE_CORETEXT_P_H
+#define QFONTENGINE_CORETEXT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qfontengine_p.h>
+#include <private/qcore_mac_p.h>
+#include <QtCore/qloggingcategory.h>
+
+#ifdef Q_OS_MACOS
+#include <ApplicationServices/ApplicationServices.h>
+#else
+#include <CoreText/CoreText.h>
+#include <CoreGraphics/CoreGraphics.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcQpaFonts)
+
+class Q_GUI_EXPORT QCoreTextFontEngine : public QFontEngine
+{
+ Q_GADGET
+
+public:
+ QCoreTextFontEngine(CTFontRef font, const QFontDef &def);
+ QCoreTextFontEngine(CGFontRef font, const QFontDef &def);
+ ~QCoreTextFontEngine();
+
+ glyph_t glyphIndex(uint ucs4) const override;
+ bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override;
+ void recalcAdvances(QGlyphLayout *, ShaperFlags) const override;
+
+ glyph_metrics_t boundingBox(const QGlyphLayout &glyphs) override;
+ glyph_metrics_t boundingBox(glyph_t glyph) override;
+
+ QFixed capHeight() const override;
+ QFixed xHeight() const override;
+ qreal maxCharWidth() const override;
+ QFixed averageCharWidth() const override;
+
+ void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs,
+ QPainterPath *path, QTextItem::RenderFlags) override;
+
+ bool canRender(const QChar *string, int len) const override;
+
+ int synthesized() const override { return synthesisFlags; }
+ bool supportsSubPixelPositions() const override { return true; }
+
+ QFixed lineThickness() const override;
+ QFixed underlinePosition() const override;
+
+ void draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight);
+
+ FaceId faceId() const override;
+ bool getSfntTableData(uint /*tag*/, uchar * /*buffer*/, uint * /*length*/) const override;
+ void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) override;
+ QImage alphaMapForGlyph(glyph_t, QFixed subPixelPosition) override;
+ QImage alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &t) override;
+ QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, const QTransform &t) override;
+ glyph_metrics_t alphaMapBoundingBox(glyph_t glyph, QFixed, const QTransform &matrix, GlyphFormat) override;
+ QImage bitmapForGlyph(glyph_t, QFixed subPixelPosition, const QTransform &t, const QColor &color) override;
+ QFixed emSquareSize() const override;
+ void doKerning(QGlyphLayout *g, ShaperFlags flags) const override;
+
+ bool supportsTransformation(const QTransform &transform) const override;
+ bool expectsGammaCorrectedBlending() const override;
+
+ QFontEngine *cloneWithSize(qreal pixelSize) const override;
+ Qt::HANDLE handle() const override;
+ int glyphMargin(QFontEngine::GlyphFormat format) override { Q_UNUSED(format); return 0; }
+
+ QFontEngine::Properties properties() const override;
+
+ enum FontSmoothing { Disabled, Subpixel, Grayscale };
+ Q_ENUM(FontSmoothing);
+
+ static FontSmoothing fontSmoothing();
+ static qreal fontSmoothingGamma();
+
+ static bool ct_getSfntTable(void *user_data, uint tag, uchar *buffer, uint *length);
+ static QFont::Weight qtWeightFromCFWeight(float value);
+
+ static QCoreTextFontEngine *create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference);
+
+protected:
+ QCoreTextFontEngine(const QFontDef &def);
+ void init();
+ QImage imageForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &m, const QColor &color = QColor());
+ void loadAdvancesForGlyphs(QVarLengthArray<CGGlyph> &cgGlyphs, QGlyphLayout *glyphs) const;
+ bool hasColorGlyphs() const;
+ bool shouldAntialias() const;
+ bool shouldSmoothFont() const;
+ void initializeHeightMetrics() const override;
+
+ QCFType<CTFontRef> ctfont;
+ QCFType<CGFontRef> cgFont;
+ int synthesisFlags;
+ CGAffineTransform transform;
+ QFixed avgCharWidth;
+ QFixed underlineThickness;
+ QFixed underlinePos;
+ QFontEngine::FaceId face_id;
+ mutable bool kerningPairsLoaded;
+};
+
+CGAffineTransform Q_GUI_EXPORT qt_transform_from_fontdef(const QFontDef &fontDef);
+
+QT_END_NAMESPACE
+
+#endif // QFONTENGINE_CORETEXT_P_H