diff options
author | Tor Arne Vestbø <tor.arne.vestbo@qt.io> | 2020-03-31 12:25:59 +0200 |
---|---|---|
committer | Tor Arne Vestbø <tor.arne.vestbo@qt.io> | 2020-05-27 17:51:24 +0200 |
commit | ce2bc0e2d5b1d647db1e1726ecd434cc8a55accf (patch) | |
tree | 004e90f6cb9a79e76f82d248a3eb8951e6095161 /src/gui/text/coretext | |
parent | cbb9c53704dca4fdaba75e1e1fd354cff058876b (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.pri | 19 | ||||
-rw-r--r-- | src/gui/text/coretext/qcoretextfontdatabase.mm | 798 | ||||
-rw-r--r-- | src/gui/text/coretext/qcoretextfontdatabase_p.h | 116 | ||||
-rw-r--r-- | src/gui/text/coretext/qfontengine_coretext.mm | 1060 | ||||
-rw-r--r-- | src/gui/text/coretext/qfontengine_coretext_p.h | 159 |
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 |