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