Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcoretextfontdatabase.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#include "qglobal.h"
6
7#include <sys/param.h>
8
9#if defined(Q_OS_MACOS)
10#import <AppKit/AppKit.h>
11#import <IOKit/graphics/IOGraphicsLib.h>
12#elif defined(QT_PLATFORM_UIKIT)
13#import <UIKit/UIFont.h>
14#endif
15
16#include <QtCore/qelapsedtimer.h>
17#include <QtCore/private/qcore_mac_p.h>
18
21#if QT_CONFIG(settings)
22#include <QtCore/QSettings>
23#endif
24#include <QtCore/QtEndian>
25#ifndef QT_NO_FREETYPE
26#include <QtGui/private/qfontengine_ft_p.h>
27#endif
28
29#include <QtGui/qpa/qwindowsysteminterface.h>
30
32
33using namespace Qt::StringLiterals;
34
35QT_IMPL_METATYPE_EXTERN_TAGGED(QCFType<CGFontRef>, QCFType_CGFontRef)
36QT_IMPL_METATYPE_EXTERN_TAGGED(QCFType<CFURLRef>, QCFType_CFURLRef)
37
38// this could become a list of all languages used for each writing
39// system, instead of using the single most common language.
40static const char languageForWritingSystem[][8] = {
41 "", // Any
42 "en", // Latin
43 "el", // Greek
44 "ru", // Cyrillic
45 "hy", // Armenian
46 "he", // Hebrew
47 "ar", // Arabic
48 "syr", // Syriac
49 "div", // Thaana
50 "hi", // Devanagari
51 "bn", // Bengali
52 "pa", // Gurmukhi
53 "gu", // Gujarati
54 "or", // Oriya
55 "ta", // Tamil
56 "te", // Telugu
57 "kn", // Kannada
58 "ml", // Malayalam
59 "si", // Sinhala
60 "th", // Thai
61 "lo", // Lao
62 "bo", // Tibetan
63 "my", // Myanmar
64 "ka", // Georgian
65 "km", // Khmer
66 "zh-Hans", // SimplifiedChinese
67 "zh-Hant", // TraditionalChinese
68 "ja", // Japanese
69 "ko", // Korean
70 "vi", // Vietnamese
71 "", // Symbol
72 "sga", // Ogham
73 "non", // Runic
74 "man" // N'Ko
75};
76enum { LanguageCount = sizeof languageForWritingSystem / sizeof *languageForWritingSystem };
77
78QCoreTextFontDatabase::QCoreTextFontDatabase()
79 : m_hasPopulatedAliases(false)
80{
81#if defined(Q_OS_MACOS)
82 m_fontSetObserver = QMacNotificationObserver(nil, NSFontSetChangedNotification, [] {
83 qCDebug(lcQpaFonts) << "Fonts have changed";
84 QPlatformFontDatabase::repopulateFontDatabase();
85 });
86#endif
87}
88
89QCoreTextFontDatabase::~QCoreTextFontDatabase()
90{
91 qDeleteAll(m_themeFonts);
92}
93
94CTFontDescriptorRef descriptorForFamily(const QString &familyName)
95{
96 return CTFontDescriptorCreateWithAttributes(CFDictionaryRef(@{
97 (id)kCTFontFamilyNameAttribute: familyName.toNSString()
98 }));
99}
100void QCoreTextFontDatabase::populateFontDatabase()
101{
102 QMacAutoReleasePool pool;
103
104 qCDebug(lcQpaFonts) << "Populating font database...";
105 QElapsedTimer elapsed;
106 if (lcQpaFonts().isDebugEnabled())
107 elapsed.start();
108
109 QCFType<CFArrayRef> familyNames = CTFontManagerCopyAvailableFontFamilyNames();
110 for (NSString *familyName in familyNames.as<const NSArray *>())
111 QPlatformFontDatabase::registerFontFamily(QString::fromNSString(familyName));
112
113 // Some fonts has special handling since macOS Catalina: It is available
114 // on the platform, so that it may be used by applications directly, but does not
115 // get enumerated. Since there are no alternatives, we hardcode it.
116 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSCatalina
117 && !qEnvironmentVariableIsSet("QT_NO_HARDCODED_FALLBACK_FONTS")) {
118 m_hardcodedFallbackFonts[QChar::Script_Adlam] = QStringLiteral("Noto Sans Adlam");
119 m_hardcodedFallbackFonts[QChar::Script_Ahom] = QStringLiteral("Noto Serif Ahom");
120 m_hardcodedFallbackFonts[QChar::Script_Avestan] = QStringLiteral("Noto Sans Avestan");
121 m_hardcodedFallbackFonts[QChar::Script_Balinese] = QStringLiteral("Noto Serif Balinese");
122 m_hardcodedFallbackFonts[QChar::Script_Bamum] = QStringLiteral("Noto Sans Bamum");
123 m_hardcodedFallbackFonts[QChar::Script_BassaVah] = QStringLiteral("Noto Sans Bassa Vah");
124 m_hardcodedFallbackFonts[QChar::Script_Batak] = QStringLiteral("Noto Sans Batak");
125 m_hardcodedFallbackFonts[QChar::Script_Bhaiksuki] = QStringLiteral("Noto Sans Bhaiksuki");
126 m_hardcodedFallbackFonts[QChar::Script_Brahmi] = QStringLiteral("Noto Sans Brahmi");
127 m_hardcodedFallbackFonts[QChar::Script_Buginese] = QStringLiteral("Noto Sans Buginese");
128 m_hardcodedFallbackFonts[QChar::Script_Buhid] = QStringLiteral("Noto Sans Buhid");
129 m_hardcodedFallbackFonts[QChar::Script_Carian] = QStringLiteral("Noto Sans Carian");
130 m_hardcodedFallbackFonts[QChar::Script_CaucasianAlbanian] = QStringLiteral("Noto Sans Caucasian Albanian");
131 m_hardcodedFallbackFonts[QChar::Script_Chakma] = QStringLiteral("Noto Sans Chakma");
132 m_hardcodedFallbackFonts[QChar::Script_Cham] = QStringLiteral("Noto Sans Cham");
133 m_hardcodedFallbackFonts[QChar::Script_Coptic] = QStringLiteral("Noto Sans Coptic");
134 m_hardcodedFallbackFonts[QChar::Script_Cuneiform] = QStringLiteral("Noto Sans Cuneiform");
135 m_hardcodedFallbackFonts[QChar::Script_Cypriot] = QStringLiteral("Noto Sans Cypriot");
136 m_hardcodedFallbackFonts[QChar::Script_Duployan] = QStringLiteral("Noto Sans Duployan");
137 m_hardcodedFallbackFonts[QChar::Script_EgyptianHieroglyphs] = QStringLiteral("Noto Sans Egyptian Hieroglyphs");
138 m_hardcodedFallbackFonts[QChar::Script_Elbasan] = QStringLiteral("Noto Sans Elbasan");
139 m_hardcodedFallbackFonts[QChar::Script_Glagolitic] = QStringLiteral("Noto Sans Glagolitic");
140 m_hardcodedFallbackFonts[QChar::Script_Gothic] = QStringLiteral("Noto Sans Gothic");
141 m_hardcodedFallbackFonts[QChar::Script_HanifiRohingya] = QStringLiteral("Noto Sans Hanifi Rohingya");
142 m_hardcodedFallbackFonts[QChar::Script_Hanunoo] = QStringLiteral("Noto Sans Hanunoo");
143 m_hardcodedFallbackFonts[QChar::Script_Hatran] = QStringLiteral("Noto Sans Hatran");
144 m_hardcodedFallbackFonts[QChar::Script_ImperialAramaic] = QStringLiteral("Noto Sans Imperial Aramaic");
145 m_hardcodedFallbackFonts[QChar::Script_InscriptionalPahlavi] = QStringLiteral("Noto Sans Inscriptional Pahlavi");
146 m_hardcodedFallbackFonts[QChar::Script_InscriptionalParthian] = QStringLiteral("Noto Sans Inscriptional Parthian");
147 m_hardcodedFallbackFonts[QChar::Script_Javanese] = QStringLiteral("Noto Sans Javanese");
148 m_hardcodedFallbackFonts[QChar::Script_Kaithi] = QStringLiteral("Noto Sans Kaithi");
149 m_hardcodedFallbackFonts[QChar::Script_KayahLi] = QStringLiteral("Noto Sans Kayah Li");
150 m_hardcodedFallbackFonts[QChar::Script_Kharoshthi] = QStringLiteral("Noto Sans Kharoshthi");
151 m_hardcodedFallbackFonts[QChar::Script_Khojki] = QStringLiteral("Noto Sans Khojki");
152 m_hardcodedFallbackFonts[QChar::Script_Khudawadi] = QStringLiteral("Noto Sans Khudawadi");
153 m_hardcodedFallbackFonts[QChar::Script_Lepcha] = QStringLiteral("Noto Sans Lepcha");
154 m_hardcodedFallbackFonts[QChar::Script_Limbu] = QStringLiteral("Noto Sans Limbu");
155 m_hardcodedFallbackFonts[QChar::Script_LinearA] = QStringLiteral("Noto Sans Linear A");
156 m_hardcodedFallbackFonts[QChar::Script_LinearB] = QStringLiteral("Noto Sans Linear B");
157 m_hardcodedFallbackFonts[QChar::Script_Lisu] = QStringLiteral("Noto Sans Lisu");
158 m_hardcodedFallbackFonts[QChar::Script_Lycian] = QStringLiteral("Noto Sans Lycian");
159 m_hardcodedFallbackFonts[QChar::Script_Lydian] = QStringLiteral("Noto Sans Lydian");
160 m_hardcodedFallbackFonts[QChar::Script_Mahajani] = QStringLiteral("Noto Sans Mahajani");
161 m_hardcodedFallbackFonts[QChar::Script_Mandaic] = QStringLiteral("Noto Sans Mandaic");
162 m_hardcodedFallbackFonts[QChar::Script_Manichaean] = QStringLiteral("Noto Sans Manichaean");
163 m_hardcodedFallbackFonts[QChar::Script_Marchen] = QStringLiteral("Noto Sans Marchen");
164 m_hardcodedFallbackFonts[QChar::Script_MendeKikakui] = QStringLiteral("Noto Sans Mende Kikakui");
165 m_hardcodedFallbackFonts[QChar::Script_MeroiticCursive] = QStringLiteral("Noto Sans Meroitic");
166 m_hardcodedFallbackFonts[QChar::Script_MeroiticHieroglyphs] = QStringLiteral("Noto Sans Meroitic");
167 m_hardcodedFallbackFonts[QChar::Script_Miao] = QStringLiteral("Noto Sans Miao");
168 m_hardcodedFallbackFonts[QChar::Script_Modi] = QStringLiteral("Noto Sans Modi");
169 m_hardcodedFallbackFonts[QChar::Script_Mongolian] = QStringLiteral("Noto Sans Mongolian");
170 m_hardcodedFallbackFonts[QChar::Script_Mro] = QStringLiteral("Noto Sans Mro");
171 m_hardcodedFallbackFonts[QChar::Script_MeeteiMayek] = QStringLiteral("Noto Sans Meetei Mayek");
172 m_hardcodedFallbackFonts[QChar::Script_Multani] = QStringLiteral("Noto Sans Multani");
173 m_hardcodedFallbackFonts[QChar::Script_Nabataean] = QStringLiteral("Noto Sans Nabataean");
174 m_hardcodedFallbackFonts[QChar::Script_Newa] = QStringLiteral("Noto Sans Newa");
175 m_hardcodedFallbackFonts[QChar::Script_NewTaiLue] = QStringLiteral("Noto Sans New Tai Lue");
176 m_hardcodedFallbackFonts[QChar::Script_Nko] = QStringLiteral("Noto Sans Nko");
177 m_hardcodedFallbackFonts[QChar::Script_OlChiki] = QStringLiteral("Noto Sans Ol Chiki");
178 m_hardcodedFallbackFonts[QChar::Script_OldHungarian] = QStringLiteral("Noto Sans Old Hungarian");
179 m_hardcodedFallbackFonts[QChar::Script_OldItalic] = QStringLiteral("Noto Sans Old Italic");
180 m_hardcodedFallbackFonts[QChar::Script_OldNorthArabian] = QStringLiteral("Noto Sans Old North Arabian");
181 m_hardcodedFallbackFonts[QChar::Script_OldPermic] = QStringLiteral("Noto Sans Old Permic");
182 m_hardcodedFallbackFonts[QChar::Script_OldPersian] = QStringLiteral("Noto Sans Old Persian");
183 m_hardcodedFallbackFonts[QChar::Script_OldSouthArabian] = QStringLiteral("Noto Sans Old South Arabian");
184 m_hardcodedFallbackFonts[QChar::Script_OldTurkic] = QStringLiteral("Noto Sans Old Turkic");
185 m_hardcodedFallbackFonts[QChar::Script_Osage] = QStringLiteral("Noto Sans Osage");
186 m_hardcodedFallbackFonts[QChar::Script_Osmanya] = QStringLiteral("Noto Sans Osmanya");
187 m_hardcodedFallbackFonts[QChar::Script_PahawhHmong] = QStringLiteral("Noto Sans Pahawh Hmong");
188 m_hardcodedFallbackFonts[QChar::Script_Palmyrene] = QStringLiteral("Noto Sans Palmyrene");
189 m_hardcodedFallbackFonts[QChar::Script_PauCinHau] = QStringLiteral("Noto Sans Pau Cin Hau");
190 m_hardcodedFallbackFonts[QChar::Script_PhagsPa] = QStringLiteral("Noto Sans PhagsPa");
191 m_hardcodedFallbackFonts[QChar::Script_Phoenician] = QStringLiteral("Noto Sans Phoenician");
192 m_hardcodedFallbackFonts[QChar::Script_PsalterPahlavi] = QStringLiteral("Noto Sans Psalter Pahlavi");
193 m_hardcodedFallbackFonts[QChar::Script_Rejang] = QStringLiteral("Noto Sans Rejang");
194 m_hardcodedFallbackFonts[QChar::Script_Samaritan] = QStringLiteral("Noto Sans Samaritan");
195 m_hardcodedFallbackFonts[QChar::Script_Saurashtra] = QStringLiteral("Noto Sans Saurashtra");
196 m_hardcodedFallbackFonts[QChar::Script_Sharada] = QStringLiteral("Noto Sans Sharada");
197 m_hardcodedFallbackFonts[QChar::Script_Siddham] = QStringLiteral("Noto Sans Siddham");
198 m_hardcodedFallbackFonts[QChar::Script_SoraSompeng] = QStringLiteral("Noto Sans Sora Sompeng");
199 m_hardcodedFallbackFonts[QChar::Script_Sundanese] = QStringLiteral("Noto Sans Sundanese");
200 m_hardcodedFallbackFonts[QChar::Script_SylotiNagri] = QStringLiteral("Noto Sans Syloti Nagri");
201 m_hardcodedFallbackFonts[QChar::Script_Tagalog] = QStringLiteral("Noto Sans Tagalog");
202 m_hardcodedFallbackFonts[QChar::Script_Tagbanwa] = QStringLiteral("Noto Sans Tagbanwa");
203 m_hardcodedFallbackFonts[QChar::Script_Takri] = QStringLiteral("Noto Sans Takri");
204 m_hardcodedFallbackFonts[QChar::Script_TaiLe] = QStringLiteral("Noto Sans Tai Le");
205 m_hardcodedFallbackFonts[QChar::Script_TaiTham] = QStringLiteral("Noto Sans Tai Tham");
206 m_hardcodedFallbackFonts[QChar::Script_TaiViet] = QStringLiteral("Noto Sans Tai Viet");
207 m_hardcodedFallbackFonts[QChar::Script_Thaana] = QStringLiteral("Noto Sans Thaana");
208 m_hardcodedFallbackFonts[QChar::Script_Tifinagh] = QStringLiteral("Noto Sans Tifinagh");
209 m_hardcodedFallbackFonts[QChar::Script_Tirhuta] = QStringLiteral("Noto Sans Tirhuta");
210 m_hardcodedFallbackFonts[QChar::Script_Ugaritic] = QStringLiteral("Noto Sans Ugaritic");
211 m_hardcodedFallbackFonts[QChar::Script_Vai] = QStringLiteral("Noto Sans Vai");
212 m_hardcodedFallbackFonts[QChar::Script_WarangCiti] = QStringLiteral("Noto Sans Warang Citi");
213 m_hardcodedFallbackFonts[QChar::Script_Wancho] = QStringLiteral("Noto Sans Wancho");
214 m_hardcodedFallbackFonts[QChar::Script_Yi] = QStringLiteral("Noto Sans Yi");
215 }
216
217 qCDebug(lcQpaFonts) << "Populating available families took" << elapsed.restart() << "ms";
218
219 populateThemeFonts();
220
221 for (auto familyName : m_systemFontDescriptors.keys()) {
222 for (auto fontDescriptor : m_systemFontDescriptors.value(familyName))
223 populateFromDescriptor(fontDescriptor, familyName);
224 }
225
226 // The font database now has a reference to the original descriptors
227 m_systemFontDescriptors.clear();
228
229 qCDebug(lcQpaFonts) << "Populating system descriptors took" << elapsed.restart() << "ms";
230
231 Q_ASSERT(!m_hasPopulatedAliases);
232}
233
234bool QCoreTextFontDatabase::populateFamilyAliases(const QString &missingFamily)
235{
236#if defined(Q_OS_MACOS)
237 if (isFamilyPopulated(missingFamily)) {
238 // We got here because one of the other properties of the font mismatched,
239 // for example the style, so there's no point in populating font aliases.
240 return false;
241 }
242
243 if (m_hasPopulatedAliases)
244 return false;
245
246 // There's no API to go from a localized family name to its non-localized
247 // name, so we have to resort to enumerating all the available fonts and
248 // doing a reverse lookup.
249
250 qCDebug(lcQpaFonts) << "Populating family aliases...";
251 QElapsedTimer elapsed;
252 elapsed.start();
253
254 QString nonLocalizedMatch;
255 QCFType<CFArrayRef> familyNames = CTFontManagerCopyAvailableFontFamilyNames();
256 NSFontManager *fontManager = NSFontManager.sharedFontManager;
257 for (NSString *familyName in familyNames.as<const NSArray *>()) {
258 NSString *localizedFamilyName = [fontManager localizedNameForFamily:familyName face:nil];
259 if (![localizedFamilyName isEqual:familyName]) {
260 QString nonLocalizedFamily = QString::fromNSString(familyName);
261 QString localizedFamily = QString::fromNSString(localizedFamilyName);
262 QPlatformFontDatabase::registerAliasToFontFamily(nonLocalizedFamily, localizedFamily);
263 if (localizedFamily == missingFamily)
264 nonLocalizedMatch = nonLocalizedFamily;
265 }
266 }
267 m_hasPopulatedAliases = true;
268
269 if (lcQpaFonts().isWarningEnabled()) {
270 QString warningMessage;
271 QDebug msg(&warningMessage);
272
273 msg << "Populating font family aliases took" << elapsed.restart() << "ms.";
274 if (!nonLocalizedMatch.isNull())
275 msg << "Replace uses of" << missingFamily << "with its non-localized name" << nonLocalizedMatch;
276 else
277 msg << "Replace uses of missing font family" << missingFamily << "with one that exists";
278 msg << "to avoid this cost.";
279
280 qCWarning(lcQpaFonts) << qPrintable(warningMessage);
281 }
282
283 return true;
284#else
285 Q_UNUSED(missingFamily);
286 return false;
287#endif
288}
289
291{
292 return descriptorForFamily(QString::fromLatin1(familyName));
293}
294
295void QCoreTextFontDatabase::populateFamily(const QString &familyName)
296{
297 qCDebug(lcQpaFonts) << "Populating family" << familyName;
298
299 // A single family might match several different fonts with different styles.
300 // We need to add them all so that the font database has the full picture,
301 // as once a family has been populated we will not populate it again.
302 QCFType<CTFontDescriptorRef> familyDescriptor = descriptorForFamily(familyName);
303 QCFType<CFArrayRef> matchingFonts = CTFontDescriptorCreateMatchingFontDescriptors(familyDescriptor, nullptr);
304 if (!matchingFonts) {
305 qCWarning(lcQpaFonts) << "QCoreTextFontDatabase: Found no matching fonts for family" << familyName;
306 return;
307 }
308
309 const int numFonts = CFArrayGetCount(matchingFonts);
310 for (int i = 0; i < numFonts; ++i)
311 populateFromDescriptor(CTFontDescriptorRef(CFArrayGetValueAtIndex(matchingFonts, i)), familyName);
312}
313
314void QCoreTextFontDatabase::invalidate()
315{
316 qCDebug(lcQpaFonts) << "Invalidating font database";
317 m_hasPopulatedAliases = false;
318
319 qDeleteAll(m_themeFonts);
320 m_themeFonts.clear();
321 QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>();
322}
323
336
337#ifndef QT_NO_DEBUG_STREAM
338Q_DECL_UNUSED static inline QDebug operator<<(QDebug debug, const FontDescription &fd)
339{
340 QDebugStateSaver saver(debug);
341 return debug.nospace() << "FontDescription("
342 << "familyName=" << QString(fd.familyName)
343 << ", styleName=" << QString(fd.styleName)
344 << ", foundry=" << fd.foundryName
345 << ", weight=" << fd.weight
346 << ", style=" << fd.style
347 << ", stretch=" << fd.stretch
348 << ", pointSize=" << fd.pointSize
349 << ", fixedPitch=" << fd.fixedPitch
350 << ", writingSystems=" << fd.writingSystems
351 << ")";
352}
353#endif
354
355static void getFontDescription(CTFontDescriptorRef font, FontDescription *fd)
356{
357 QCFType<CFDictionaryRef> styles = (CFDictionaryRef) CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute);
358
359 fd->foundryName = QStringLiteral("CoreText");
360 fd->familyName = (CFStringRef) CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute);
361 fd->styleName = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute);
362 fd->weight = QFont::Normal;
363 fd->style = QFont::StyleNormal;
364 fd->stretch = QFont::Unstretched;
365 fd->fixedPitch = false;
366 fd->colorFont = false;
367
368
369
370 if (QCFType<CTFontRef> tempFont = CTFontCreateWithFontDescriptor(font, 0.0, 0)) {
371 uint tag = QFont::Tag("OS/2").value();
372 CTFontRef tempFontRef = tempFont;
373 void *userData = reinterpret_cast<void *>(&tempFontRef);
374 uint length = 128;
375 QVarLengthArray<uchar, 128> os2Table(length);
376 if (QCoreTextFontEngine::ct_getSfntTable(userData, tag, os2Table.data(), &length) && length >= 86) {
377 if (length > uint(os2Table.length())) {
378 os2Table.resize(length);
379 if (!QCoreTextFontEngine::ct_getSfntTable(userData, tag, os2Table.data(), &length))
380 Q_UNREACHABLE();
381 Q_ASSERT(length >= 86);
382 }
383 fd->writingSystems = QPlatformFontDatabase::writingSystemsFromOS2Table(reinterpret_cast<const char *>(os2Table.data()), length);
384 }
385 }
386
387 if (styles) {
388 if (CFNumberRef weightValue = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontWeightTrait)) {
389 double normalizedWeight;
390 if (CFNumberGetValue(weightValue, kCFNumberFloat64Type, &normalizedWeight))
391 fd->weight = QCoreTextFontEngine::qtWeightFromCFWeight(float(normalizedWeight));
392 }
393 if (CFNumberRef italic = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontSlantTrait)) {
394 double d;
395 if (CFNumberGetValue(italic, kCFNumberDoubleType, &d)) {
396 if (d > 0.0)
397 fd->style = QFont::StyleItalic;
398 }
399 }
400 if (CFNumberRef symbolic = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontSymbolicTrait)) {
401 int d;
402 if (CFNumberGetValue(symbolic, kCFNumberSInt32Type, &d)) {
403 if (d & kCTFontColorGlyphsTrait)
404 fd->colorFont = true;
405
406 if (d & kCTFontMonoSpaceTrait)
407 fd->fixedPitch = true;
408 if (d & kCTFontExpandedTrait)
409 fd->stretch = QFont::Expanded;
410 else if (d & kCTFontCondensedTrait)
411 fd->stretch = QFont::Condensed;
412 }
413 }
414 }
415
416 if (QCFType<CFNumberRef> size = (CFNumberRef) CTFontDescriptorCopyAttribute(font, kCTFontSizeAttribute)) {
417 if (CFNumberIsFloatType(size)) {
418 double d;
419 CFNumberGetValue(size, kCFNumberDoubleType, &d);
420 fd->pointSize = d;
421 } else {
422 int i;
423 CFNumberGetValue(size, kCFNumberIntType, &i);
424 fd->pointSize = i;
425 }
426 }
427
428 if (QCFType<CFArrayRef> languages = (CFArrayRef) CTFontDescriptorCopyAttribute(font, kCTFontLanguagesAttribute)) {
429 CFIndex length = CFArrayGetCount(languages);
430 for (int i = 1; i < LanguageCount; ++i) {
431 if (!*languageForWritingSystem[i])
432 continue;
433 QCFString lang = CFStringCreateWithCString(NULL, languageForWritingSystem[i], kCFStringEncodingASCII);
434 if (CFArrayContainsValue(languages, CFRangeMake(0, length), lang))
435 fd->writingSystems.setSupported(QFontDatabase::WritingSystem(i));
436 }
437 }
438}
439
440void QCoreTextFontDatabase::populateFromDescriptor(CTFontDescriptorRef font,
441 const QString &familyName,
442 QFontDatabasePrivate::ApplicationFont *applicationFont,
443 PopulateDescriptorFlag flag)
444{
445 FontDescription fd;
446 getFontDescription(font, &fd);
447
448 // Note: The familyName we are registering, and the family name of the font descriptor, may not
449 // match, as CTFontDescriptorCreateMatchingFontDescriptors will return descriptors for replacement
450 // fonts if a font family does not have any fonts available on the system.
451 QString family = !familyName.isNull() ? familyName : static_cast<QString>(fd.familyName);
452
453 if (applicationFont != nullptr) {
454 QFontDatabasePrivate::ApplicationFont::Properties properties;
455 properties.familyName = family;
456 properties.styleName = fd.styleName;
457 properties.weight = fd.weight;
458 properties.stretch = fd.stretch;
459 properties.style = fd.style;
460
461 applicationFont->properties.append(properties);
462 }
463
464 const bool forceColorFont = flag == PopulateDescriptorFlag::ForceColorFont;
465
466 CFRetain(font);
467 QPlatformFontDatabase::registerFont(family, fd.styleName, fd.foundryName, fd.weight, fd.style, fd.stretch,
468 true /* antialiased */, true /* scalable */, 0 /* pixelSize, ignored as font is scalable */,
469 fd.fixedPitch, forceColorFont || fd.colorFont, fd.writingSystems, (void *)font);
470}
471
472static NSString * const kQtFontDataAttribute = @"QtFontDataAttribute";
473static NSString * const kQtFontInstanceIndexAttribute = @"QtFontInstanceIndexAttribute";
474
475template <typename T>
476T *descriptorAttribute(CTFontDescriptorRef descriptor, CFStringRef name)
477{
478 return [static_cast<T *>(CTFontDescriptorCopyAttribute(descriptor, name)) autorelease];
479}
480
481void QCoreTextFontDatabase::releaseHandle(void *handle)
482{
483 CTFontDescriptorRef descriptor = static_cast<CTFontDescriptorRef>(handle);
484 if (NSValue *fontDataValue = descriptorAttribute<NSValue>(descriptor, (CFStringRef)kQtFontDataAttribute)) {
485 QByteArray *fontData = static_cast<QByteArray *>(fontDataValue.pointerValue);
486 delete fontData;
487 }
488 CFRelease(descriptor);
489}
490
491extern CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef);
492
493template <>
494QFontEngine *QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>::fontEngine(const QFontDef &fontDef, void *usrPtr)
495{
496 QMacAutoReleasePool pool;
497
498 QCFType<CTFontDescriptorRef> descriptor = QCFType<CTFontDescriptorRef>::constructFromGet(
499 static_cast<CTFontDescriptorRef>(usrPtr));
500
501 // Since we do not pass in the destination DPI to CoreText when making
502 // the font, we need to pass in a point size which is scaled to include
503 // the DPI. The default DPI for the screen is 72, thus the scale factor
504 // is destinationDpi / 72, but since pixelSize = pointSize / 72 * dpi,
505 // the pixelSize is actually the scaled point size for the destination
506 // DPI, and we can use that directly.
507 qreal scaledPointSize = fontDef.pixelSize;
508
509 CGAffineTransform matrix = qt_transform_from_fontdef(fontDef);
510
511 if (!fontDef.variableAxisValues.isEmpty()) {
512 QCFType<CFMutableDictionaryRef> variations = CFDictionaryCreateMutable(nullptr,
513 fontDef.variableAxisValues.size(),
514 &kCFTypeDictionaryKeyCallBacks,
515 &kCFTypeDictionaryValueCallBacks);
516 for (auto it = fontDef.variableAxisValues.constBegin();
517 it != fontDef.variableAxisValues.constEnd();
518 ++it) {
519 const quint32 tag = it.key().value();
520 const float value = it.value();
521 QCFType<CFNumberRef> tagRef = CFNumberCreate(nullptr, kCFNumberIntType, &tag);
522 QCFType<CFNumberRef> valueRef = CFNumberCreate(nullptr, kCFNumberFloatType, &value);
523
524 CFDictionarySetValue(variations, tagRef, valueRef);
525 }
526 QCFType<CFDictionaryRef> attributes = CFDictionaryCreate(nullptr,
527 (const void **) &kCTFontVariationAttribute,
528 (const void **) &variations,
529 1,
530 &kCFTypeDictionaryKeyCallBacks,
531 &kCFTypeDictionaryValueCallBacks);
532 descriptor = CTFontDescriptorCreateCopyWithAttributes(descriptor, attributes);
533 }
534
535 if (QCFType<CTFontRef> font = CTFontCreateWithFontDescriptor(descriptor, scaledPointSize, &matrix))
536 return new QCoreTextFontEngine(font, fontDef);
537
538 return nullptr;
539}
540
541#ifndef QT_NO_FREETYPE
542template <>
543bool QCoreTextFontDatabaseEngineFactory<QFontEngineFT>::shouldForceColorFont(CTFontDescriptorRef descriptor) const
544{
545#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20501
546 FT_Library library = qt_getFreetype();
547 FT_Face face;
548 FT_Error error;
549 if (NSValue *fontDataValue = descriptorAttribute<NSValue>(descriptor, (CFStringRef)kQtFontDataAttribute)) {
550 const QByteArray *fontData = static_cast<const QByteArray *>(fontDataValue.pointerValue);
551 error = FT_New_Memory_Face(library,
552 (const FT_Byte *)fontData->constData(),
553 fontData->size(),
554 0,
555 &face);
556 } else if (NSURL *url = descriptorAttribute<NSURL>(descriptor, kCTFontURLAttribute)) {
557 Q_ASSERT(url.fileURL);
558 const QString faceFileName{QString::fromNSString(url.path)};
559 const QString styleName = QCFString(CTFontDescriptorCopyAttribute(descriptor,
560 kCTFontStyleNameAttribute));
561 int index = QFreetypeFace::getFaceIndexByStyleName(faceFileName, styleName);
562
563 const QByteArray fname = faceFileName.toUtf8();
564 error = FT_New_Face(library, fname.constData(), index, &face);
565 } else {
566 error = FT_Err_Missing_Property;
567 }
568
569 if (error != FT_Err_Ok) {
570 qCWarning(lcQpaFonts) << "Failed to create face for" << descriptor;
571 return false;
572 }
573
574 const bool isColor = FT_HAS_COLOR(face);
575 FT_Done_Face(face);
576
577 return isColor;
578#else
579 Q_UNUSED(descriptor);
580 return false;
581#endif
582}
583
584template <>
585QFontEngine *QCoreTextFontDatabaseEngineFactory<QFontEngineFT>::fontEngine(const QFontDef &fontDef, void *usrPtr)
586{
587 QMacAutoReleasePool pool;
588
589 CTFontDescriptorRef descriptor = static_cast<CTFontDescriptorRef>(usrPtr);
590
591 int instanceIndex = 0;
592 if (NSNumber *numberRef = descriptorAttribute<NSNumber>(descriptor, (CFStringRef)kQtFontInstanceIndexAttribute))
593 instanceIndex = numberRef.integerValue;
594
595 if (NSValue *fontDataValue = descriptorAttribute<NSValue>(descriptor, (CFStringRef)kQtFontDataAttribute)) {
596 QByteArray *fontData = static_cast<QByteArray *>(fontDataValue.pointerValue);
597 return QFontEngineFT::create(*fontData,
598 fontDef.pixelSize,
599 static_cast<QFont::HintingPreference>(fontDef.hintingPreference),
600 fontDef.variableAxisValues,
601 instanceIndex);
602 } else if (NSURL *url = descriptorAttribute<NSURL>(descriptor, kCTFontURLAttribute)) {
603 QFontEngine::FaceId faceId;
604
605 Q_ASSERT(url.fileURL);
606 QString faceFileName{QString::fromNSString(url.path)};
607 faceId.filename = faceFileName.toUtf8();
608
609 QString styleName = QCFString(CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute));
610 faceId.index = QFreetypeFace::getFaceIndexByStyleName(faceFileName, styleName);
611 faceId.instanceIndex = instanceIndex;
612 faceId.variableAxes = fontDef.variableAxisValues;
613
614 return QFontEngineFT::create(fontDef, faceId);
615 }
616 // We end up here with a descriptor does not contain Qt font data or kCTFontURLAttribute.
617 // Since the FT engine can't deal with a descriptor with just a NSFontNameAttribute,
618 // we should return nullptr.
619 return nullptr;
620}
621#endif
622
623template <class T>
624bool QCoreTextFontDatabaseEngineFactory<T>::shouldForceColorFont(CTFontDescriptorRef descriptor) const
625{
626 Q_UNUSED(descriptor);
627 return false;
628}
629
630template <class T>
631QFontEngine *QCoreTextFontDatabaseEngineFactory<T>::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
632{
633 return T::create(fontData, pixelSize, hintingPreference, {});
634}
635
636// Explicitly instantiate so that we don't need the plugin to involve FreeType
637template class QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>;
638#ifndef QT_NO_FREETYPE
639template class QCoreTextFontDatabaseEngineFactory<QFontEngineFT>;
640#endif
641
642CFArrayRef fallbacksForDescriptor(CTFontDescriptorRef descriptor)
643{
644 QCFType<CTFontRef> font = CTFontCreateWithFontDescriptor(descriptor, 0.0, nullptr);
645 if (!font) {
646 qCWarning(lcQpaFonts) << "Failed to create fallback font for" << descriptor;
647 return nullptr;
648 }
649
650 CFArrayRef cascadeList = CFArrayRef(CTFontCopyDefaultCascadeListForLanguages(font,
651 (CFArrayRef)NSLocale.preferredLanguages));
652
653 if (!cascadeList) {
654 qCWarning(lcQpaFonts) << "Failed to create fallback cascade list for" << descriptor;
655 return nullptr;
656 }
657
658 return cascadeList;
659}
660
661CFArrayRef QCoreTextFontDatabase::fallbacksForFamily(const QString &family)
662{
663 if (family.isEmpty())
664 return nullptr;
665
666 QCFType<CTFontDescriptorRef> fontDescriptor = descriptorForFamily(family);
667 if (!fontDescriptor) {
668 qCWarning(lcQpaFonts) << "Failed to create fallback font descriptor for" << family;
669 return nullptr;
670 }
671
672 // If the font is not available we want to fall back to the style hint.
673 // By creating a matching font descriptor we can verify whether the font
674 // is available or not, and avoid CTFontCreateWithFontDescriptor picking
675 // a default font for us based on incomplete information.
676 fontDescriptor = CTFontDescriptorCreateMatchingFontDescriptor(fontDescriptor, 0);
677 if (!fontDescriptor)
678 return nullptr;
679
680 return fallbacksForDescriptor(fontDescriptor);
681}
682
684{
685 static const CGFloat kDefaultSizeForRequestedUIType = 0.0;
686 QCFType<CTFontRef> ctFont = CTFontCreateUIFontForLanguage(
687 uiType, kDefaultSizeForRequestedUIType, nullptr);
688 return CTFontCopyFontDescriptor(ctFont);
689}
690
691CTFontDescriptorRef descriptorForStyle(QFont::StyleHint styleHint)
692{
693 switch (styleHint) {
694 case QFont::SansSerif: return descriptorForFamily("Helvetica");
695 case QFont::Serif: return descriptorForFamily("Times New Roman");
696 case QFont::Monospace: return descriptorForFamily("Menlo");
697#ifdef Q_OS_MACOS
698 case QFont::Cursive: return descriptorForFamily("Apple Chancery");
699#endif
700 case QFont::Fantasy: return descriptorForFamily("Zapfino");
701 case QFont::TypeWriter: return descriptorForFamily("American Typewriter");
702 case QFont::AnyStyle: Q_FALLTHROUGH();
703 case QFont::System: return descriptorForFontType(kCTFontUIFontSystem);
704 default: return nullptr; // No matching font on this platform
705 }
706}
707
708QStringList QCoreTextFontDatabase::fallbacksForScript(QFontDatabasePrivate::ExtendedScript script) const
709{
710 if (script == QFontDatabasePrivate::Script_Emoji)
711 return QStringList{} << QStringLiteral(".Apple Color Emoji UI");
712 else
713 return QStringList{};
714}
715
716QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family,
717 QFont::Style style,
718 QFont::StyleHint styleHint,
719 QFontDatabasePrivate::ExtendedScript script) const
720{
721 Q_UNUSED(style);
722
723 qCDebug(lcQpaFonts).nospace() << "Resolving fallbacks families for"
724 << (!family.isEmpty() ? qPrintable(" family '%1' with"_L1.arg(family)) : "")
725 << " style hint " << styleHint;
726
727 QMacAutoReleasePool pool;
728
729 QStringList fallbackList = fallbacksForScript(script);
730
731 QCFType<CFArrayRef> fallbackFonts = fallbacksForFamily(family);
732 if (!fallbackFonts || !CFArrayGetCount(fallbackFonts)) {
733 // We were not able to find a fallback for the specific family,
734 // or the family was empty, so we fall back to the style hint.
735 if (!family.isEmpty())
736 qCDebug(lcQpaFonts) << "No fallbacks found. Using style hint instead";
737
738 if (QCFType<CTFontDescriptorRef> styleDescriptor = descriptorForStyle(styleHint)) {
739 CFMutableArrayRef tmp = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
740 CFArrayAppendValue(tmp, styleDescriptor);
741 QCFType<CFArrayRef> styleFallbacks = fallbacksForDescriptor(styleDescriptor);
742 CFArrayAppendArray(tmp, styleFallbacks, CFRangeMake(0, CFArrayGetCount(styleFallbacks)));
743 fallbackFonts = tmp;
744 }
745 }
746
747 if (!fallbackFonts)
748 return fallbackList;
749
750 const int numberOfFallbacks = CFArrayGetCount(fallbackFonts);
751 for (int i = 0; i < numberOfFallbacks; ++i) {
752 auto fallbackDescriptor = CTFontDescriptorRef(CFArrayGetValueAtIndex(fallbackFonts, i));
753 auto fallbackFamilyName = QCFString(CTFontDescriptorCopyAttribute(fallbackDescriptor, kCTFontFamilyNameAttribute));
754
755 if (!isFamilyPopulated(fallbackFamilyName)) {
756 // We need to populate, or at least register the fallback fonts,
757 // otherwise the Qt font database may not know they exist.
758 if (isPrivateFontFamily(fallbackFamilyName))
759 const_cast<QCoreTextFontDatabase *>(this)->populateFromDescriptor(fallbackDescriptor);
760 else
761 registerFontFamily(fallbackFamilyName);
762 }
763
764 fallbackList.append(fallbackFamilyName);
765 }
766
767 // Some fallback fonts will have have an order in the list returned
768 // by Core Text that would indicate they should be preferred for e.g.
769 // Arabic, or Emoji, while in reality only supporting a tiny subset
770 // of the required glyphs, or representing them by question marks.
771 // Move these to the end, so that the proper fonts are preferred.
772 for (const char *family : { ".Apple Symbols Fallback", ".Noto Sans Universal" }) {
773 int index = fallbackList.indexOf(QLatin1StringView(family));
774 if (index >= 0)
775 fallbackList.move(index, fallbackList.size() - 1);
776 }
777
778#if defined(Q_OS_MACOS)
779 // Since we are only returning a list of default fonts for the current language, we do not
780 // cover all Unicode completely. This was especially an issue for some of the common script
781 // symbols such as mathematical symbols, currency or geometric shapes. To minimize the risk
782 // of missing glyphs, we add Arial Unicode MS as a final fail safe, since this covers most
783 // of Unicode 2.1.
784 if (!fallbackList.contains(QStringLiteral("Arial Unicode MS")))
785 fallbackList.append(QStringLiteral("Arial Unicode MS"));
786 // Since some symbols (specifically Braille) are not in Arial Unicode MS, we
787 // add Apple Symbols to cover those too.
788 if (!fallbackList.contains(QStringLiteral("Apple Symbols")))
789 fallbackList.append(QStringLiteral("Apple Symbols"));
790 // Some Noto* fonts are not automatically enumerated by system, despite being the main
791 // fonts for their writing system.
792 if (script < int(QChar::ScriptCount)) {
793 QString hardcodedFont = m_hardcodedFallbackFonts.value(QChar::Script(script));
794 if (!hardcodedFont.isEmpty() && !fallbackList.contains(hardcodedFont)) {
795 if (!isFamilyPopulated(hardcodedFont)) {
796 if (!m_privateFamilies.contains(hardcodedFont)) {
797 QCFType<CTFontDescriptorRef> familyDescriptor = descriptorForFamily(hardcodedFont);
798 QCFType<CFArrayRef> matchingFonts = CTFontDescriptorCreateMatchingFontDescriptors(familyDescriptor, nullptr);
799 if (matchingFonts) {
800 const int numFonts = CFArrayGetCount(matchingFonts);
801 for (int i = 0; i < numFonts; ++i)
802 const_cast<QCoreTextFontDatabase *>(this)->populateFromDescriptor(CTFontDescriptorRef(CFArrayGetValueAtIndex(matchingFonts, i)),
803 hardcodedFont);
804
805 fallbackList.append(hardcodedFont);
806 }
807
808 // Register as private family even if the font is not found, in order to avoid
809 // redoing the check later. In later calls, the font will then just be ignored.
810 m_privateFamilies.insert(hardcodedFont);
811 }
812 } else {
813 fallbackList.append(hardcodedFont);
814 }
815 }
816 }
817#endif
818
819 extern QStringList qt_sort_families_by_writing_system(QFontDatabasePrivate::ExtendedScript, const QStringList &);
820 fallbackList = qt_sort_families_by_writing_system(script, fallbackList);
821
822 qCDebug(lcQpaFonts).nospace() << "Fallback families ordered by script " << script << ": " << fallbackList;
823
824 return fallbackList;
825}
826
827QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont)
828{
829 QCFType<CFArrayRef> fonts;
830
831 if (!fontData.isEmpty()) {
832 QCFType<CFDataRef> fontDataReference = fontData.toRawCFData();
833 fonts = CTFontManagerCreateFontDescriptorsFromData(fontDataReference);
834 } else {
835 QCFType<CFURLRef> fontURL = QUrl::fromLocalFile(fileName).toCFURL();
836 fonts = CTFontManagerCreateFontDescriptorsFromURL(fontURL);
837 }
838
839 if (fonts) {
840 CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
841 const int count = CFArrayGetCount(fonts);
842
843 for (int i = 0; i < count; ++i) {
844 CTFontDescriptorRef descriptor = CTFontDescriptorRef(CFArrayGetValueAtIndex(fonts, i));
845
846 NSDictionary *attributes = !fontData.isEmpty()
847 ? @{
848 kQtFontDataAttribute: [NSValue valueWithPointer:new QByteArray(fontData)],
849 kQtFontInstanceIndexAttribute: @(i)
850 }
851 : @{
852 kQtFontInstanceIndexAttribute: @(i)
853 };
854
855 QCFType<CTFontDescriptorRef> copiedDescriptor = CTFontDescriptorCreateCopyWithAttributes(descriptor, (CFDictionaryRef)attributes);
856 CFArrayAppendValue(array, copiedDescriptor);
857 }
858
859 fonts = array;
860 } else {
861 return QStringList();
862 }
863
864 QStringList families;
865 const int numFonts = CFArrayGetCount(fonts);
866 for (int i = 0; i < numFonts; ++i) {
867 CTFontDescriptorRef fontDescriptor = CTFontDescriptorRef(CFArrayGetValueAtIndex(fonts, i));
868 populateFromDescriptor(fontDescriptor,
869 QString(),
870 applicationFont,
871 shouldForceColorFont(fontDescriptor)
872 ? PopulateDescriptorFlag::ForceColorFont
873 : PopulateDescriptorFlag::None);
874 QCFType<CFStringRef> familyName = CFStringRef(CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontFamilyNameAttribute));
875 families.append(QString::fromCFString(familyName));
876 }
877
878 // Note: We don't do font matching via CoreText for application fonts, so we don't
879 // need to enable font matching for them via CTFontManagerEnableFontDescriptors.
880
881 return families;
882}
883
884bool QCoreTextFontDatabase::isPrivateFontFamily(const QString &family) const
885{
886 if (family.startsWith(u'.') || family == "LastResort"_L1 || m_privateFamilies.contains(family))
887 return true;
888
889 return QPlatformFontDatabase::isPrivateFontFamily(family);
890}
891
892static CTFontUIFontType fontTypeFromTheme(QPlatformTheme::Font f)
893{
894 switch (f) {
895 case QPlatformTheme::SystemFont:
896 return kCTFontUIFontSystem;
897
898 case QPlatformTheme::MenuFont:
899 case QPlatformTheme::MenuBarFont:
900 case QPlatformTheme::MenuItemFont:
901 return kCTFontUIFontMenuItem;
902
903 case QPlatformTheme::MessageBoxFont:
904 return kCTFontUIFontEmphasizedSystem;
905
906 case QPlatformTheme::LabelFont:
907 return kCTFontUIFontSystem;
908
909 case QPlatformTheme::TipLabelFont:
910 return kCTFontUIFontToolTip;
911
912 case QPlatformTheme::StatusBarFont:
913 return kCTFontUIFontSystem;
914
915 case QPlatformTheme::TitleBarFont:
916 return kCTFontUIFontWindowTitle;
917
918 case QPlatformTheme::MdiSubWindowTitleFont:
919 return kCTFontUIFontSystem;
920
921 case QPlatformTheme::DockWidgetTitleFont:
922 return kCTFontUIFontSmallSystem;
923
924 case QPlatformTheme::PushButtonFont:
925 return kCTFontUIFontPushButton;
926
927 case QPlatformTheme::CheckBoxFont:
928 case QPlatformTheme::RadioButtonFont:
929 return kCTFontUIFontSystem;
930
931 case QPlatformTheme::ToolButtonFont:
932 return kCTFontUIFontSmallToolbar;
933
934 case QPlatformTheme::ItemViewFont:
935 return kCTFontUIFontSystem;
936
937 case QPlatformTheme::ListViewFont:
938 return kCTFontUIFontViews;
939
940 case QPlatformTheme::HeaderViewFont:
941 return kCTFontUIFontSmallSystem;
942
943 case QPlatformTheme::ListBoxFont:
944 return kCTFontUIFontViews;
945
946 case QPlatformTheme::ComboMenuItemFont:
947 return kCTFontUIFontSystem;
948
949 case QPlatformTheme::ComboLineEditFont:
950 return kCTFontUIFontViews;
951
952 case QPlatformTheme::SmallFont:
953 return kCTFontUIFontSmallSystem;
954
955 case QPlatformTheme::MiniFont:
956 return kCTFontUIFontMiniSystem;
957
958 case QPlatformTheme::FixedFont:
959 return kCTFontUIFontUserFixedPitch;
960
961 default:
962 return kCTFontUIFontSystem;
963 }
964}
965
966static CTFontDescriptorRef fontDescriptorFromTheme(QPlatformTheme::Font f)
967{
968#if defined(QT_PLATFORM_UIKIT)
969 // Use Dynamic Type to resolve theme fonts if possible, to get
970 // correct font sizes and style based on user configuration.
971 NSString *textStyle = 0;
972 switch (f) {
973 case QPlatformTheme::TitleBarFont:
974 case QPlatformTheme::HeaderViewFont:
975 textStyle = UIFontTextStyleHeadline;
976 break;
977 case QPlatformTheme::MdiSubWindowTitleFont:
978 textStyle = UIFontTextStyleSubheadline;
979 break;
980 case QPlatformTheme::TipLabelFont:
981 case QPlatformTheme::SmallFont:
982 textStyle = UIFontTextStyleFootnote;
983 break;
984 case QPlatformTheme::MiniFont:
985 textStyle = UIFontTextStyleCaption2;
986 break;
987 case QPlatformTheme::FixedFont:
988 // Fall back to regular code path, as iOS doesn't provide
989 // an appropriate text style for this theme font.
990 break;
991 default:
992 textStyle = UIFontTextStyleBody;
993 break;
994 }
995
996 if (textStyle) {
997 UIFontDescriptor *desc = [UIFontDescriptor preferredFontDescriptorWithTextStyle:textStyle];
998 return static_cast<CTFontDescriptorRef>(CFBridgingRetain(desc));
999 }
1000#endif // QT_PLATFORM_UIKIT
1001
1002 // macOS default case and iOS fallback case
1003 return descriptorForFontType(fontTypeFromTheme(f));
1004}
1005
1006void QCoreTextFontDatabase::populateThemeFonts()
1007{
1008 QMacAutoReleasePool pool;
1009
1010 if (!m_themeFonts.isEmpty())
1011 return;
1012
1013 QElapsedTimer elapsed;
1014 if (lcQpaFonts().isDebugEnabled())
1015 elapsed.start();
1016
1017 qCDebug(lcQpaFonts) << "Populating theme fonts...";
1018
1019 for (long f = QPlatformTheme::SystemFont; f < QPlatformTheme::NFonts; f++) {
1020 QPlatformTheme::Font themeFont = static_cast<QPlatformTheme::Font>(f);
1021 QCFType<CTFontDescriptorRef> fontDescriptor = fontDescriptorFromTheme(themeFont);
1022 FontDescription fd;
1023 getFontDescription(fontDescriptor, &fd);
1024
1025 // We might get here from QFontDatabase::systemFont() or QPlatformTheme::font(),
1026 // before the font database has initialized itself and populated all available
1027 // families. As a result, we can't populate the descriptor at this time, as that
1028 // would result in the font database having > 0 families, which would result in
1029 // skipping the initialization and population of all other font families. Instead
1030 // we store the descriptors for later and populate them during populateFontDatabase().
1031
1032 bool haveRegisteredFamily = m_systemFontDescriptors.contains(fd.familyName);
1033 qCDebug(lcQpaFonts) << "Got" << (haveRegisteredFamily ? "already registered" : "unseen")
1034 << "family" << fd.familyName << "for" << themeFont;
1035
1036 if (!haveRegisteredFamily) {
1037 // We need to register all weights and variants of the theme font,
1038 // as the user might tweak the returned QFont before use.
1039 QList<QCFType<CTFontDescriptorRef>> themeFontVariants;
1040
1041 auto addFontVariants = [&](CTFontDescriptorRef descriptor) {
1042 QCFType<CFArrayRef> matchingDescriptors = CTFontDescriptorCreateMatchingFontDescriptors(descriptor, nullptr);
1043 const int matchingDescriptorsCount = matchingDescriptors ? CFArrayGetCount(matchingDescriptors) : 0;
1044 qCDebug(lcQpaFonts) << "Enumerating font variants based on" << id(descriptor)
1045 << "resulted in" << matchingDescriptorsCount << "matching descriptors"
1046 << matchingDescriptors.as<NSArray*>();
1047
1048 for (int i = 0; i < matchingDescriptorsCount; ++i) {
1049 auto matchingDescriptor = CTFontDescriptorRef(CFArrayGetValueAtIndex(matchingDescriptors, i));
1050 themeFontVariants.append(QCFType<CTFontDescriptorRef>::constructFromGet(matchingDescriptor));
1051 }
1052 };
1053
1054 // Try populating the font variants based on its UI design trait, if available
1055 auto fontTraits = QCFType<CFDictionaryRef>(CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontTraitsAttribute));
1056 static const NSString *kUIFontDesignTrait = @"NSCTFontUIFontDesignTrait";
1057 if (id uiFontDesignTrait = fontTraits.as<NSDictionary*>()[kUIFontDesignTrait]) {
1058 QCFType<CTFontDescriptorRef> designTraitDescriptor = CTFontDescriptorCreateWithAttributes(
1059 CFDictionaryRef(@{ (id)kCTFontTraitsAttribute: @{ kUIFontDesignTrait: uiFontDesignTrait }
1060 }));
1061 addFontVariants(designTraitDescriptor);
1062 }
1063
1064 if (themeFontVariants.isEmpty()) {
1065 // Fall back to populating variants based on the family name alone
1066 QCFType<CTFontDescriptorRef> familyDescriptor = descriptorForFamily(fd.familyName);
1067 addFontVariants(familyDescriptor);
1068 }
1069
1070 if (themeFontVariants.isEmpty()) {
1071 qCDebug(lcQpaFonts) << "No theme font variants found, falling back to single variant descriptor";
1072 themeFontVariants.append(fontDescriptor);
1073 }
1074
1075 m_systemFontDescriptors.insert(fd.familyName, themeFontVariants);
1076 }
1077
1078 QFont *font = new QFont(fd.familyName, fd.pointSize, fd.weight, fd.style == QFont::StyleItalic);
1079 m_themeFonts.insert(themeFont, font);
1080 }
1081
1082 qCDebug(lcQpaFonts) << "Populating theme fonts took" << elapsed.restart() << "ms";
1083}
1084
1085QFont *QCoreTextFontDatabase::themeFont(QPlatformTheme::Font f) const
1086{
1087 // The code paths via QFontDatabase::systemFont() or QPlatformTheme::font()
1088 // do not ensure that the font database has been populated, so we need to
1089 // manually populate the theme fonts lazily here just in case.
1090 const_cast<QCoreTextFontDatabase*>(this)->populateThemeFonts();
1091
1092 return m_themeFonts.value(f, nullptr);
1093}
1094
1095QFont QCoreTextFontDatabase::defaultFont() const
1096{
1097 return QFont(*themeFont(QPlatformTheme::SystemFont));
1098}
1099
1100bool QCoreTextFontDatabase::fontsAlwaysScalable() const
1101{
1102 return true;
1103}
1104
1105QList<int> QCoreTextFontDatabase::standardSizes() const
1106{
1107 QList<int> ret;
1108 static const unsigned short standard[] =
1109 { 9, 10, 11, 12, 13, 14, 18, 24, 36, 48, 64, 72, 96, 144, 288, 0 };
1110 ret.reserve(int(sizeof(standard) / sizeof(standard[0])));
1111 const unsigned short *sizes = standard;
1112 while (*sizes) ret << *sizes++;
1113 return ret;
1114}
1115
1116bool QCoreTextFontDatabase::supportsColrv0Fonts() const
1117{
1118 return true;
1119}
1120
1121bool QCoreTextFontDatabase::supportsVariableApplicationFonts() const
1122{
1123 return true;
1124}
1125
1126QT_END_NAMESPACE
Combined button and popup list for selecting options.
CFArrayRef fallbacksForDescriptor(CTFontDescriptorRef descriptor)
CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef)
static NSString *const kQtFontDataAttribute
static Q_DECL_UNUSED QDebug operator<<(QDebug debug, const FontDescription &fd)
CTFontDescriptorRef descriptorForFamily(const QString &familyName)
static void getFontDescription(CTFontDescriptorRef font, FontDescription *fd)
static CTFontUIFontType fontTypeFromTheme(QPlatformTheme::Font f)
static CTFontDescriptorRef fontDescriptorFromTheme(QPlatformTheme::Font f)
CTFontDescriptorRef descriptorForFamily(const char *familyName)
static NSString *const kQtFontInstanceIndexAttribute
CTFontDescriptorRef descriptorForFontType(CTFontUIFontType uiType)
T * descriptorAttribute(CTFontDescriptorRef descriptor, CFStringRef name)
CTFontDescriptorRef descriptorForStyle(QFont::StyleHint styleHint)
QSupportedWritingSystems writingSystems