1// Copyright (C) 2019 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
4#include "qfontconfigdatabase_p.h"
5#include "qfontenginemultifontconfig_p.h"
6
7#include <QtGui/private/qfontengine_ft_p.h>
8
9#include <QtCore/QList>
10#include <QtCore/QElapsedTimer>
11#include <QtCore/QFile>
12
13#include <qpa/qplatformnativeinterface.h>
14#include <qpa/qplatformscreen.h>
15#include <qpa/qplatformintegration.h>
16#include <qpa/qplatformservices.h>
17
18#include <QtGui/private/qguiapplication_p.h>
19
20#include <QtGui/qguiapplication.h>
21
22#include <QtCore/private/qduplicatetracker_p.h>
23
24#include <fontconfig/fontconfig.h>
25#if FC_VERSION >= 20402
26#include <fontconfig/fcfreetype.h>
27#endif
28
29QT_BEGIN_NAMESPACE
30
31Q_DECLARE_LOGGING_CATEGORY(lcFontDb)
32
33static inline int mapToQtWeightForRange(int fcweight, int fcLower, int fcUpper, int qtLower, int qtUpper)
34{
35 return qtLower + ((fcweight - fcLower) * (qtUpper - qtLower)) / (fcUpper - fcLower);
36}
37
38static inline int weightFromFcWeight(int fcweight)
39{
40 // Font Config uses weights from 0 to 215 (the highest enum value) while QFont ranges from
41 // 1 to 1000. The spacing between the values for the enums are uneven so a linear mapping from
42 // Font Config values to Qt would give surprising results. So, we do a piecewise linear
43 // mapping. This ensures that where there is a corresponding enum on both sides (for example
44 // FC_WEIGHT_DEMIBOLD and QFont::DemiBold) we map one to the other but other values map
45 // to intermediate Qt weights.
46
47 if (fcweight <= FC_WEIGHT_THIN)
48 return QFont::Thin;
49 if (fcweight <= FC_WEIGHT_ULTRALIGHT)
50 return mapToQtWeightForRange(fcweight, FC_WEIGHT_THIN, FC_WEIGHT_ULTRALIGHT, qtLower: QFont::Thin, qtUpper: QFont::ExtraLight);
51 if (fcweight <= FC_WEIGHT_LIGHT)
52 return mapToQtWeightForRange(fcweight, FC_WEIGHT_ULTRALIGHT, FC_WEIGHT_LIGHT, qtLower: QFont::ExtraLight, qtUpper: QFont::Light);
53 if (fcweight <= FC_WEIGHT_NORMAL)
54 return mapToQtWeightForRange(fcweight, FC_WEIGHT_LIGHT, FC_WEIGHT_NORMAL, qtLower: QFont::Light, qtUpper: QFont::Normal);
55 if (fcweight <= FC_WEIGHT_MEDIUM)
56 return mapToQtWeightForRange(fcweight, FC_WEIGHT_NORMAL, FC_WEIGHT_MEDIUM, qtLower: QFont::Normal, qtUpper: QFont::Medium);
57 if (fcweight <= FC_WEIGHT_DEMIBOLD)
58 return mapToQtWeightForRange(fcweight, FC_WEIGHT_MEDIUM, FC_WEIGHT_DEMIBOLD, qtLower: QFont::Medium, qtUpper: QFont::DemiBold);
59 if (fcweight <= FC_WEIGHT_BOLD)
60 return mapToQtWeightForRange(fcweight, FC_WEIGHT_DEMIBOLD, FC_WEIGHT_BOLD, qtLower: QFont::DemiBold, qtUpper: QFont::Bold);
61 if (fcweight <= FC_WEIGHT_ULTRABOLD)
62 return mapToQtWeightForRange(fcweight, FC_WEIGHT_BOLD, FC_WEIGHT_ULTRABOLD, qtLower: QFont::Bold, qtUpper: QFont::ExtraBold);
63 if (fcweight <= FC_WEIGHT_BLACK)
64 return mapToQtWeightForRange(fcweight, FC_WEIGHT_ULTRABOLD, FC_WEIGHT_BLACK, qtLower: QFont::ExtraBold, qtUpper: QFont::Black);
65 if (fcweight <= FC_WEIGHT_ULTRABLACK)
66 return mapToQtWeightForRange(fcweight, FC_WEIGHT_BLACK, FC_WEIGHT_ULTRABLACK, qtLower: QFont::Black,
67 QFONT_WEIGHT_MAX);
68 return QFONT_WEIGHT_MAX;
69}
70
71static inline int stretchFromFcWidth(int fcwidth)
72{
73 // Font Config enums for width match pretty closely with those used by Qt so just use
74 // Font Config values directly while enforcing the same limits imposed by QFont.
75 const int maxStretch = 4000;
76 int qtstretch;
77 if (fcwidth < 1)
78 qtstretch = 1;
79 else if (fcwidth > maxStretch)
80 qtstretch = maxStretch;
81 else
82 qtstretch = fcwidth;
83
84 return qtstretch;
85}
86
87static const char specialLanguages[][6] = {
88 "", // Unknown
89 "", // Inherited
90 "", // Common
91 "en", // Latin
92 "el", // Greek
93 "ru", // Cyrillic
94 "hy", // Armenian
95 "he", // Hebrew
96 "ar", // Arabic
97 "syr", // Syriac
98 "dv", // Thaana
99 "hi", // Devanagari
100 "bn", // Bengali
101 "pa", // Gurmukhi
102 "gu", // Gujarati
103 "or", // Oriya
104 "ta", // Tamil
105 "te", // Telugu
106 "kn", // Kannada
107 "ml", // Malayalam
108 "si", // Sinhala
109 "th", // Thai
110 "lo", // Lao
111 "bo", // Tibetan
112 "my", // Myanmar
113 "ka", // Georgian
114 "ko", // Hangul
115 "am", // Ethiopic
116 "chr", // Cherokee
117 "cr", // CanadianAboriginal
118 "sga", // Ogham
119 "non", // Runic
120 "km", // Khmer
121 "mn", // Mongolian
122 "ja", // Hiragana
123 "ja", // Katakana
124 "zh-TW", // Bopomofo
125 "", // Han
126 "ii", // Yi
127 "ett", // OldItalic
128 "got", // Gothic
129 "en", // Deseret
130 "fil", // Tagalog
131 "hnn", // Hanunoo
132 "bku", // Buhid
133 "tbw", // Tagbanwa
134 "cop", // Coptic
135 "lif", // Limbu
136 "tdd", // TaiLe
137 "grc", // LinearB
138 "uga", // Ugaritic
139 "en", // Shavian
140 "so", // Osmanya
141 "grc", // Cypriot
142 "", // Braille
143 "bug", // Buginese
144 "khb", // NewTaiLue
145 "cu", // Glagolitic
146 "shi", // Tifinagh
147 "syl", // SylotiNagri
148 "peo", // OldPersian
149 "pra", // Kharoshthi
150 "ban", // Balinese
151 "akk", // Cuneiform
152 "phn", // Phoenician
153 "lzh", // PhagsPa
154 "man", // Nko
155 "su", // Sundanese
156 "lep", // Lepcha
157 "sat", // OlChiki
158 "vai", // Vai
159 "saz", // Saurashtra
160 "eky", // KayahLi
161 "rej", // Rejang
162 "xlc", // Lycian
163 "xcr", // Carian
164 "xld", // Lydian
165 "cjm", // Cham
166 "nod", // TaiTham
167 "blt", // TaiViet
168 "ae", // Avestan
169 "egy", // EgyptianHieroglyphs
170 "smp", // Samaritan
171 "lis", // Lisu
172 "bax", // Bamum
173 "jv", // Javanese
174 "mni", // MeeteiMayek
175 "arc", // ImperialAramaic
176 "xsa", // OldSouthArabian
177 "xpr", // InscriptionalParthian
178 "pal", // InscriptionalPahlavi
179 "otk", // OldTurkic
180 "bh", // Kaithi
181 "bbc", // Batak
182 "pra", // Brahmi
183 "myz", // Mandaic
184 "ccp", // Chakma
185 "xmr", // MeroiticCursive
186 "xmr", // MeroiticHieroglyphs
187 "hmd", // Miao
188 "sa", // Sharada
189 "srb", // SoraSompeng
190 "doi", // Takri
191 "lez", // CaucasianAlbanian
192 "bsq", // BassaVah
193 "fr", // Duployan
194 "sq", // Elbasan
195 "sa", // Grantha
196 "hnj", // PahawhHmong
197 "sd", // Khojki
198 "lab", // LinearA
199 "hi", // Mahajani
200 "xmn", // Manichaean
201 "men", // MendeKikakui
202 "mr", // Modi
203 "mru", // Mro
204 "xna", // OldNorthArabian
205 "arc", // Nabataean
206 "arc", // Palmyrene
207 "ctd", // PauCinHau
208 "kv", // OldPermic
209 "pal", // PsalterPahlavi
210 "sa", // Siddham
211 "sd", // Khudawadi
212 "mai", // Tirhuta
213 "hoc", // WarangCiti
214 "", // Ahom
215 "", // AnatolianHieroglyphs
216 "", // Hatran
217 "", // Multani
218 "", // OldHungarian
219 "", // SignWriting
220 "", // Adlam
221 "", // Bhaiksuki
222 "", // Marchen
223 "", // Newa
224 "", // Osage
225 "", // Tangut
226 "", // MasaramGondi
227 "", // Nushu
228 "", // Soyombo
229 "", // ZanabazarSquare
230 "", // Dogra
231 "", // GunjalaGondi
232 "", // HanifiRohingya
233 "", // Makasar
234 "", // Medefaidrin
235 "", // OldSogdian
236 "", // Sogdian
237 "", // Elymaic
238 "", // Nandinagari
239 "", // NyiakengPuachueHmong
240 "", // Wancho
241 "", // Chorasmian
242 "", // DivesAkuru
243 "", // KhitanSmallScript
244 "", // Yezidi
245 "", // CyproMinoan
246 "", // OldUyghur
247 "", // Tangsa
248 "", // Toto
249 "", // Vithkuqi
250 "", // Kawi
251 "", // NagMundari
252 "", // Garay
253 "", // GurungKhema
254 "", // KiratRai
255 "", // OlOnal
256 "", // Sunuwar
257 "", // Todhri
258 "", // TuluTigalari
259};
260static_assert(sizeof specialLanguages / sizeof *specialLanguages == QChar::ScriptCount);
261
262// this could become a list of all languages used for each writing
263// system, instead of using the single most common language.
264static const char languageForWritingSystem[][6] = {
265 "", // Any
266 "en", // Latin
267 "el", // Greek
268 "ru", // Cyrillic
269 "hy", // Armenian
270 "he", // Hebrew
271 "ar", // Arabic
272 "syr", // Syriac
273 "div", // Thaana
274 "hi", // Devanagari
275 "bn", // Bengali
276 "pa", // Gurmukhi
277 "gu", // Gujarati
278 "or", // Oriya
279 "ta", // Tamil
280 "te", // Telugu
281 "kn", // Kannada
282 "ml", // Malayalam
283 "si", // Sinhala
284 "th", // Thai
285 "lo", // Lao
286 "bo", // Tibetan
287 "my", // Myanmar
288 "ka", // Georgian
289 "km", // Khmer
290 "zh-cn", // SimplifiedChinese
291 "zh-tw", // TraditionalChinese
292 "ja", // Japanese
293 "ko", // Korean
294 "vi", // Vietnamese
295 "", // Symbol
296 "sga", // Ogham
297 "non", // Runic
298 "man" // N'Ko
299};
300static_assert(sizeof languageForWritingSystem / sizeof *languageForWritingSystem == QFontDatabase::WritingSystemsCount);
301
302#if FC_VERSION >= 20297
303// Newer FontConfig let's us sort out fonts that report certain scripts support,
304// but no open type tables for handling them correctly.
305// Check the reported script presence in the FC_CAPABILITY's "otlayout:" section.
306static const char capabilityForWritingSystem[][5] = {
307 "", // Any
308 "", // Latin
309 "", // Greek
310 "", // Cyrillic
311 "", // Armenian
312 "", // Hebrew
313 "", // Arabic
314 "syrc", // Syriac
315 "thaa", // Thaana
316 "deva", // Devanagari
317 "beng", // Bengali
318 "guru", // Gurmukhi
319 "gujr", // Gujarati
320 "orya", // Oriya
321 "taml", // Tamil
322 "telu", // Telugu
323 "knda", // Kannada
324 "mlym", // Malayalam
325 "sinh", // Sinhala
326 "", // Thai
327 "", // Lao
328 "tibt", // Tibetan
329 "mymr", // Myanmar
330 "", // Georgian
331 "khmr", // Khmer
332 "", // SimplifiedChinese
333 "", // TraditionalChinese
334 "", // Japanese
335 "", // Korean
336 "", // Vietnamese
337 "", // Symbol
338 "", // Ogham
339 "", // Runic
340 "nko " // N'Ko
341};
342static_assert(sizeof(capabilityForWritingSystem) / sizeof(*capabilityForWritingSystem) == QFontDatabase::WritingSystemsCount);
343#endif
344
345static const char *getFcFamilyForStyleHint(const QFont::StyleHint style)
346{
347 const char *stylehint = nullptr;
348 switch (style) {
349 case QFont::SansSerif:
350 stylehint = "sans-serif";
351 break;
352 case QFont::Serif:
353 stylehint = "serif";
354 break;
355 case QFont::TypeWriter:
356 case QFont::Monospace:
357 stylehint = "monospace";
358 break;
359 case QFont::Cursive:
360 stylehint = "cursive";
361 break;
362 case QFont::Fantasy:
363 stylehint = "fantasy";
364 break;
365 default:
366 break;
367 }
368 return stylehint;
369}
370
371static inline bool requiresOpenType(int writingSystem)
372{
373 return ((writingSystem >= QFontDatabase::Syriac && writingSystem <= QFontDatabase::Sinhala)
374 || writingSystem == QFontDatabase::Khmer || writingSystem == QFontDatabase::Nko);
375}
376
377static void populateFromPattern(FcPattern *pattern,
378 QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr,
379 FT_Face face = nullptr,
380 QFontconfigDatabase *db = nullptr)
381{
382 QString familyName;
383 QString familyNameLang;
384 FcChar8 *value = nullptr;
385 int weight_value;
386 int slant_value;
387 int spacing_value;
388 int width_value;
389 FcChar8 *file_value;
390 int indexValue;
391 FcChar8 *foundry_value;
392 FcChar8 *style_value;
393 FcBool scalable;
394 FcBool antialias;
395
396 if (FcPatternGetString(p: pattern, FC_FAMILY, n: 0, s: &value) != FcResultMatch)
397 return;
398
399 familyName = QString::fromUtf8(utf8: (const char *)value);
400
401 if (FcPatternGetString(p: pattern, FC_FAMILYLANG, n: 0, s: &value) == FcResultMatch)
402 familyNameLang = QString::fromUtf8(utf8: (const char *)value);
403
404 slant_value = FC_SLANT_ROMAN;
405 weight_value = FC_WEIGHT_REGULAR;
406 spacing_value = FC_PROPORTIONAL;
407 file_value = nullptr;
408 indexValue = 0;
409 scalable = FcTrue;
410
411
412 if (FcPatternGetInteger(p: pattern, FC_SLANT, n: 0, i: &slant_value) != FcResultMatch)
413 slant_value = FC_SLANT_ROMAN;
414 if (FcPatternGetInteger(p: pattern, FC_WEIGHT, n: 0, i: &weight_value) != FcResultMatch)
415 weight_value = FC_WEIGHT_REGULAR;
416 if (FcPatternGetInteger(p: pattern, FC_WIDTH, n: 0, i: &width_value) != FcResultMatch)
417 width_value = FC_WIDTH_NORMAL;
418 if (FcPatternGetInteger(p: pattern, FC_SPACING, n: 0, i: &spacing_value) != FcResultMatch)
419 spacing_value = FC_PROPORTIONAL;
420 if (FcPatternGetString(p: pattern, FC_FILE, n: 0, s: &file_value) != FcResultMatch)
421 file_value = nullptr;
422 if (FcPatternGetInteger(p: pattern, FC_INDEX, n: 0, i: &indexValue) != FcResultMatch)
423 indexValue = 0;
424 if (FcPatternGetBool(p: pattern, FC_SCALABLE, n: 0, b: &scalable) != FcResultMatch)
425 scalable = FcTrue;
426 if (FcPatternGetString(p: pattern, FC_FOUNDRY, n: 0, s: &foundry_value) != FcResultMatch)
427 foundry_value = nullptr;
428 if (FcPatternGetString(p: pattern, FC_STYLE, n: 0, s: &style_value) != FcResultMatch)
429 style_value = nullptr;
430 if (FcPatternGetBool(p: pattern,FC_ANTIALIAS,n: 0,b: &antialias) != FcResultMatch)
431 antialias = true;
432
433 QSupportedWritingSystems writingSystems;
434 FcLangSet *langset = nullptr;
435 FcResult res = FcPatternGetLangSet(p: pattern, FC_LANG, n: 0, ls: &langset);
436 if (res == FcResultMatch) {
437 bool hasLang = false;
438#if FC_VERSION >= 20297
439 FcChar8 *cap = nullptr;
440 FcResult capRes = FcResultNoMatch;
441#endif
442 for (int j = 1; j < QFontDatabase::WritingSystemsCount; ++j) {
443 const FcChar8 *lang = (const FcChar8*) languageForWritingSystem[j];
444 if (lang) {
445 FcLangResult langRes = FcLangSetHasLang(ls: langset, lang);
446 if (langRes != FcLangDifferentLang) {
447#if FC_VERSION >= 20297
448 if (*capabilityForWritingSystem[j] && requiresOpenType(writingSystem: j)) {
449 if (cap == nullptr)
450 capRes = FcPatternGetString(p: pattern, FC_CAPABILITY, n: 0, s: &cap);
451 if (capRes == FcResultMatch && strstr(haystack: reinterpret_cast<const char *>(cap), needle: capabilityForWritingSystem[j]) == nullptr)
452 continue;
453 }
454#endif
455 writingSystems.setSupported(QFontDatabase::WritingSystem(j));
456 hasLang = true;
457 }
458 }
459 }
460 if (!hasLang)
461 // none of our known languages, add it to the other set
462 writingSystems.setSupported(QFontDatabase::Other);
463 } else {
464 // we set Other to supported for symbol fonts. It makes no
465 // sense to merge these with other ones, as they are
466 // special in a way.
467 writingSystems.setSupported(QFontDatabase::Other);
468 }
469
470 FontFile *fontFile = new FontFile;
471 fontFile->fileName = QString::fromLocal8Bit(ba: (const char *)file_value);
472 fontFile->indexValue = indexValue;
473
474 QFont::Style style = (slant_value == FC_SLANT_ITALIC)
475 ? QFont::StyleItalic
476 : ((slant_value == FC_SLANT_OBLIQUE)
477 ? QFont::StyleOblique
478 : QFont::StyleNormal);
479 // Note: weight should really be an int but registerFont incorrectly uses an enum
480 QFont::Weight weight = QFont::Weight(weightFromFcWeight(fcweight: weight_value));
481
482 double pixel_size = 0;
483 if (!scalable)
484 FcPatternGetDouble (p: pattern, FC_PIXEL_SIZE, n: 0, d: &pixel_size);
485
486 bool fixedPitch = spacing_value >= FC_MONO;
487 // Note: stretch should really be an int but registerFont incorrectly uses an enum
488 QFont::Stretch stretch = QFont::Stretch(stretchFromFcWidth(fcwidth: width_value));
489 QString styleName = style_value ? QString::fromUtf8(utf8: (const char *) style_value) : QString();
490
491 if (applicationFont != nullptr) {
492 QFontDatabasePrivate::ApplicationFont::Properties properties;
493 properties.familyName = familyName;
494 properties.styleName = styleName;
495 properties.weight = weight;
496 properties.style = style;
497 properties.stretch = stretch;
498
499 applicationFont->properties.append(t: properties);
500 }
501
502 QPlatformFontDatabase::registerFont(familyname: familyName,stylename: styleName,foundryname: QLatin1StringView((const char *)foundry_value),weight,style,stretch,antialiased: antialias,scalable,pixelSize: pixel_size,fixedPitch,writingSystems,handle: fontFile);
503 if (applicationFont != nullptr && face != nullptr && db != nullptr) {
504 db->addNamedInstancesForFace(face,
505 faceIndex: indexValue,
506 family: familyName,
507 styleName,
508 weight,
509 stretch,
510 style,
511 fixedPitch,
512 writingSystems,
513 fileName: QByteArray((const char*)file_value),
514 fontData: applicationFont->data);
515 }
516
517// qDebug() << familyName << (const char *)foundry_value << weight << style << &writingSystems << scalable << true << pixel_size;
518
519 for (int k = 1; FcPatternGetString(p: pattern, FC_FAMILY, n: k, s: &value) == FcResultMatch; ++k) {
520 const QString altFamilyName = QString::fromUtf8(utf8: (const char *)value);
521 // Extra family names can be aliases or subfamilies.
522 // If it is a subfamily, register it as a separate font, so only members of the subfamily are
523 // matched when the subfamily is requested.
524 QString altStyleName;
525 if (FcPatternGetString(p: pattern, FC_STYLE, n: k, s: &value) == FcResultMatch)
526 altStyleName = QString::fromUtf8(utf8: (const char *)value);
527 else
528 altStyleName = styleName;
529
530 QString altFamilyNameLang;
531 if (FcPatternGetString(p: pattern, FC_FAMILYLANG, n: k, s: &value) == FcResultMatch)
532 altFamilyNameLang = QString::fromUtf8(utf8: (const char *)value);
533 else
534 altFamilyNameLang = familyNameLang;
535
536 if (familyNameLang == altFamilyNameLang && altStyleName != styleName) {
537 if (applicationFont != nullptr) {
538 QFontDatabasePrivate::ApplicationFont::Properties properties;
539 properties.familyName = altFamilyName;
540 properties.styleName = altStyleName;
541 properties.weight = weight;
542 properties.style = style;
543 properties.stretch = stretch;
544
545 applicationFont->properties.append(t: properties);
546 }
547 FontFile *altFontFile = new FontFile(*fontFile);
548 QPlatformFontDatabase::registerFont(familyname: altFamilyName, stylename: altStyleName, foundryname: QLatin1StringView((const char *)foundry_value),weight,style,stretch,antialiased: antialias,scalable,pixelSize: pixel_size,fixedPitch,writingSystems,handle: altFontFile);
549 } else {
550 QPlatformFontDatabase::registerAliasToFontFamily(familyName, alias: altFamilyName);
551 }
552 }
553
554}
555
556static bool isDprScaling()
557{
558 return !qFuzzyCompare(qApp->devicePixelRatio(), p2: qreal(1.0));
559}
560
561QFontconfigDatabase::~QFontconfigDatabase()
562{
563 FcConfigDestroy(config: FcConfigGetCurrent());
564}
565
566void QFontconfigDatabase::populateFontDatabase()
567{
568 FcInit();
569 FcFontSet *fonts;
570
571 {
572 FcObjectSet *os = FcObjectSetCreate();
573 FcPattern *pattern = FcPatternCreate();
574 const char *properties [] = {
575 FC_FAMILY, FC_STYLE, FC_WEIGHT, FC_SLANT,
576 FC_SPACING, FC_FILE, FC_INDEX,
577 FC_LANG, FC_CHARSET, FC_FOUNDRY, FC_SCALABLE, FC_PIXEL_SIZE,
578 FC_WIDTH, FC_FAMILYLANG,
579#if FC_VERSION >= 20297
580 FC_CAPABILITY,
581#endif
582 (const char *)nullptr
583 };
584 const char **p = properties;
585 while (*p) {
586 FcObjectSetAdd(os, object: *p);
587 ++p;
588 }
589
590#ifdef FC_VARIABLE
591 /* Support the named instance of Variable Fonts. */
592 FcPatternAddBool(p: pattern, FC_VARIABLE, FcFalse);
593#endif
594
595 fonts = FcFontList(config: nullptr, p: pattern, os);
596 FcObjectSetDestroy(os);
597 FcPatternDestroy(p: pattern);
598 if (!fonts)
599 return;
600 }
601
602 for (int i = 0; i < fonts->nfont; i++)
603 populateFromPattern(pattern: fonts->fonts[i]);
604
605 FcFontSetDestroy (s: fonts);
606
607 struct FcDefaultFont {
608 const char *qtname;
609 const char *rawname;
610 bool fixed;
611 };
612 const FcDefaultFont defaults[] = {
613 { .qtname: "Serif", .rawname: "serif", .fixed: false },
614 { .qtname: "Sans Serif", .rawname: "sans-serif", .fixed: false },
615 { .qtname: "Monospace", .rawname: "monospace", .fixed: true },
616 { .qtname: nullptr, .rawname: nullptr, .fixed: false }
617 };
618 const FcDefaultFont *f = defaults;
619 // aliases only make sense for 'common', not for any of the specials
620 QSupportedWritingSystems ws;
621 ws.setSupported(QFontDatabase::Latin);
622
623 while (f->qtname) {
624 QString familyQtName = QString::fromLatin1(ba: f->qtname);
625 registerFont(familyname: familyQtName,stylename: QString(),foundryname: QString(),weight: QFont::Normal,style: QFont::StyleNormal,stretch: QFont::Unstretched,antialiased: true,scalable: true,pixelSize: 0,fixedPitch: f->fixed,writingSystems: ws,handle: nullptr);
626 registerFont(familyname: familyQtName,stylename: QString(),foundryname: QString(),weight: QFont::Normal,style: QFont::StyleItalic,stretch: QFont::Unstretched,antialiased: true,scalable: true,pixelSize: 0,fixedPitch: f->fixed,writingSystems: ws,handle: nullptr);
627 registerFont(familyname: familyQtName,stylename: QString(),foundryname: QString(),weight: QFont::Normal,style: QFont::StyleOblique,stretch: QFont::Unstretched,antialiased: true,scalable: true,pixelSize: 0,fixedPitch: f->fixed,writingSystems: ws,handle: nullptr);
628 ++f;
629 }
630
631 //QPA has very lazy population of the font db. We want it to be initialized when
632 //QApplication is constructed, so that the population procedure can do something like this to
633 //set the default font
634// const FcDefaultFont *s = defaults;
635// QFont font("Sans Serif");
636// font.setPointSize(9);
637// QApplication::setFont(font);
638}
639
640void QFontconfigDatabase::invalidate()
641{
642 // Clear app fonts.
643 FcConfigAppFontClear(config: nullptr);
644}
645
646QFontEngineMulti *QFontconfigDatabase::fontEngineMulti(QFontEngine *fontEngine, QChar::Script script)
647{
648 return new QFontEngineMultiFontConfig(fontEngine, script);
649}
650
651namespace {
652QFontEngine::HintStyle defaultHintStyleFromMatch(QFont::HintingPreference hintingPreference, FcPattern *match, bool preferXftConf)
653{
654 switch (hintingPreference) {
655 case QFont::PreferNoHinting:
656 return QFontEngine::HintNone;
657 case QFont::PreferVerticalHinting:
658 return QFontEngine::HintLight;
659 case QFont::PreferFullHinting:
660 return QFontEngine::HintFull;
661 case QFont::PreferDefaultHinting:
662 break;
663 }
664
665 if (isDprScaling())
666 return QFontEngine::HintNone;
667
668 void *hintStyleResource =
669 QGuiApplication::platformNativeInterface()->nativeResourceForScreen(resource: "hintstyle",
670 screen: QGuiApplication::primaryScreen());
671 int xftHintStyle = int(reinterpret_cast<qintptr>(hintStyleResource));
672 if (preferXftConf && xftHintStyle > 0)
673 return QFontEngine::HintStyle(xftHintStyle - 1);
674
675 int hint_style = 0;
676 if (FcPatternGetInteger (p: match, FC_HINT_STYLE, n: 0, i: &hint_style) == FcResultMatch) {
677 switch (hint_style) {
678 case FC_HINT_NONE:
679 return QFontEngine::HintNone;
680 case FC_HINT_SLIGHT:
681 return QFontEngine::HintLight;
682 case FC_HINT_MEDIUM:
683 return QFontEngine::HintMedium;
684 case FC_HINT_FULL:
685 return QFontEngine::HintFull;
686 default:
687 Q_UNREACHABLE();
688 break;
689 }
690 }
691 if (xftHintStyle > 0)
692 return QFontEngine::HintStyle(xftHintStyle - 1);
693
694 return QFontEngine::HintFull;
695}
696
697QFontEngine::SubpixelAntialiasingType subpixelTypeFromMatch(FcPattern *match, bool preferXftConf)
698{
699 void *subpixelTypeResource =
700 QGuiApplication::platformNativeInterface()->nativeResourceForScreen(resource: "subpixeltype",
701 screen: QGuiApplication::primaryScreen());
702 int xftSubpixelType = int(reinterpret_cast<qintptr>(subpixelTypeResource));
703 if (preferXftConf && xftSubpixelType > 0)
704 return QFontEngine::SubpixelAntialiasingType(xftSubpixelType - 1);
705
706 int subpixel = FC_RGBA_UNKNOWN;
707 if (FcPatternGetInteger(p: match, FC_RGBA, n: 0, i: &subpixel) == FcResultMatch) {
708 switch (subpixel) {
709 case FC_RGBA_UNKNOWN:
710 case FC_RGBA_NONE:
711 return QFontEngine::Subpixel_None;
712 case FC_RGBA_RGB:
713 return QFontEngine::Subpixel_RGB;
714 case FC_RGBA_BGR:
715 return QFontEngine::Subpixel_BGR;
716 case FC_RGBA_VRGB:
717 return QFontEngine::Subpixel_VRGB;
718 case FC_RGBA_VBGR:
719 return QFontEngine::Subpixel_VBGR;
720 default:
721 Q_UNREACHABLE();
722 break;
723 }
724 }
725
726 if (xftSubpixelType > 0)
727 return QFontEngine::SubpixelAntialiasingType(xftSubpixelType - 1);
728
729 return QFontEngine::Subpixel_None;
730}
731} // namespace
732
733QFontEngine *QFontconfigDatabase::fontEngine(const QFontDef &f, void *usrPtr)
734{
735 if (!usrPtr)
736 return nullptr;
737
738 FontFile *fontfile = static_cast<FontFile *> (usrPtr);
739 QFontEngine::FaceId fid;
740 fid.filename = QFile::encodeName(fileName: fontfile->fileName);
741 fid.index = fontfile->indexValue;
742 fid.instanceIndex = fontfile->instanceIndex;
743 fid.variableAxes = f.variableAxisValues;
744
745 // FIXME: Unify with logic in QFontEngineFT::create()
746 QFontEngineFT *engine = new QFontEngineFT(f);
747 engine->face_id = fid;
748
749 setupFontEngine(engine, fontDef: f);
750
751 if (!engine->init(faceId: fid, antiaalias: engine->antialias, defaultFormat: engine->defaultFormat) || engine->invalid()) {
752 delete engine;
753 engine = nullptr;
754 }
755
756 return engine;
757}
758
759QFontEngine *QFontconfigDatabase::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
760{
761 QFontEngineFT *engine = static_cast<QFontEngineFT*>(QFreeTypeFontDatabase::fontEngine(fontData, pixelSize, hintingPreference));
762 if (engine == nullptr)
763 return nullptr;
764
765 setupFontEngine(engine, fontDef: engine->fontDef);
766
767 return engine;
768}
769
770QStringList QFontconfigDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
771{
772 QStringList fallbackFamilies;
773 FcPattern *pattern = FcPatternCreate();
774 if (!pattern)
775 return fallbackFamilies;
776
777 FcValue value;
778 value.type = FcTypeString;
779 const QByteArray cs = family.toUtf8();
780 value.u.s = (const FcChar8 *)cs.data();
781 FcPatternAdd(p: pattern,FC_FAMILY,value,append: true);
782
783 int slant_value = FC_SLANT_ROMAN;
784 if (style == QFont::StyleItalic)
785 slant_value = FC_SLANT_ITALIC;
786 else if (style == QFont::StyleOblique)
787 slant_value = FC_SLANT_OBLIQUE;
788 FcPatternAddInteger(p: pattern, FC_SLANT, i: slant_value);
789
790 Q_ASSERT(uint(script) < QChar::ScriptCount);
791 if (*specialLanguages[script] != '\0') {
792 FcLangSet *ls = FcLangSetCreate();
793 FcLangSetAdd(ls, lang: (const FcChar8*)specialLanguages[script]);
794 FcPatternAddLangSet(p: pattern, FC_LANG, ls);
795 FcLangSetDestroy(ls);
796 } else if (!family.isEmpty()) {
797 // If script is Common or Han, then it may include languages like CJK,
798 // we should attach system default language set to the pattern
799 // to obtain correct font fallback list (i.e. if LANG=zh_CN
800 // then we normally want to use a Chinese font for CJK text;
801 // while a Japanese font should be used for that if LANG=ja)
802 FcPattern *dummy = FcPatternCreate();
803 FcDefaultSubstitute(pattern: dummy);
804 FcChar8 *lang = nullptr;
805 FcResult res = FcPatternGetString(p: dummy, FC_LANG, n: 0, s: &lang);
806 if (res == FcResultMatch)
807 FcPatternAddString(p: pattern, FC_LANG, s: lang);
808 FcPatternDestroy(p: dummy);
809 }
810
811 const char *stylehint = getFcFamilyForStyleHint(style: styleHint);
812 if (stylehint) {
813 value.u.s = (const FcChar8 *)stylehint;
814 FcPatternAddWeak(p: pattern, FC_FAMILY, value, FcTrue);
815 }
816
817 FcConfigSubstitute(config: nullptr, p: pattern, kind: FcMatchPattern);
818 FcDefaultSubstitute(pattern);
819
820 FcResult result = FcResultMatch;
821 FcFontSet *fontSet = FcFontSort(config: nullptr,p: pattern,FcFalse,csp: nullptr,result: &result);
822 FcPatternDestroy(p: pattern);
823
824 if (fontSet) {
825 QDuplicateTracker<QString> duplicates(fontSet->nfont + 1);
826 (void)duplicates.hasSeen(s: family.toCaseFolded());
827 for (int i = 0; i < fontSet->nfont; i++) {
828 FcChar8 *value = nullptr;
829 if (FcPatternGetString(p: fontSet->fonts[i], FC_FAMILY, n: 0, s: &value) != FcResultMatch)
830 continue;
831 // capitalize(value);
832 const QString familyName = QString::fromUtf8(utf8: (const char *)value);
833 const QString familyNameCF = familyName.toCaseFolded();
834 if (!duplicates.hasSeen(s: familyNameCF)) {
835 fallbackFamilies << familyName;
836 }
837 }
838 FcFontSetDestroy(s: fontSet);
839 }
840// qDebug() << "fallbackFamilies for:" << family << style << styleHint << script << fallbackFamilies;
841
842 return fallbackFamilies;
843}
844
845static FcPattern *queryFont(const FcChar8 *file, const QByteArray &data, int id, FcBlanks *blanks, int *count, FT_Face *face)
846{
847#if FC_VERSION < 20402
848 Q_UNUSED(data);
849 *face = nullptr;
850 return FcFreeTypeQuery(file, id, blanks, count);
851#else
852 if (data.isEmpty()) {
853 *face = nullptr;
854 return FcFreeTypeQuery(file, id, blanks, count);
855 }
856
857 FT_Library lib = qt_getFreetype();
858
859 FcPattern *pattern = nullptr;
860
861 if (!FT_New_Memory_Face(library: lib, file_base: (const FT_Byte *)data.constData(), file_size: data.size(), face_index: id, aface: face)) {
862 *count = (*face)->num_faces;
863
864 pattern = FcFreeTypeQueryFace(face: *face, file, id, blanks);
865 } else {
866 *face = nullptr;
867 }
868
869 return pattern;
870#endif
871}
872
873QStringList QFontconfigDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont)
874{
875 QStringList families;
876
877 if (applicationFont != nullptr)
878 applicationFont->properties.clear();
879
880 FcFontSet *set = FcConfigGetFonts(config: nullptr, set: FcSetApplication);
881 if (!set) {
882 FcConfigAppFontAddFile(config: nullptr, file: (const FcChar8 *)":/non-existent");
883 set = FcConfigGetFonts(config: nullptr, set: FcSetApplication); // try again
884 if (!set)
885 return families;
886 }
887
888 int id = 0;
889 FcBlanks *blanks = FcConfigGetBlanks(config: nullptr);
890 int count = 0;
891
892 FcPattern *pattern;
893 do {
894 FT_Face face;
895 pattern = queryFont(file: (const FcChar8 *)QFile::encodeName(fileName).constData(),
896 data: fontData, id, blanks, count: &count, face: &face);
897 if (!pattern)
898 return families;
899
900 FcChar8 *fam = nullptr;
901 if (FcPatternGetString(p: pattern, FC_FAMILY, n: 0, s: &fam) == FcResultMatch) {
902 QString family = QString::fromUtf8(utf8: reinterpret_cast<const char *>(fam));
903 families << family;
904 }
905 populateFromPattern(pattern, applicationFont, face, db: this);
906
907 if (face)
908 FT_Done_Face(face);
909
910 FcFontSetAdd(s: set, font: pattern);
911
912 ++id;
913 } while (id < count);
914
915 return families;
916}
917
918QString QFontconfigDatabase::resolveFontFamilyAlias(const QString &family) const
919{
920 QString resolved = QFreeTypeFontDatabase::resolveFontFamilyAlias(family);
921 if (!resolved.isEmpty() && resolved != family)
922 return resolved;
923 FcPattern *pattern = FcPatternCreate();
924 if (!pattern)
925 return family;
926
927 if (!family.isEmpty()) {
928 const QByteArray cs = family.toUtf8();
929 FcPatternAddString(p: pattern, FC_FAMILY, s: (const FcChar8 *) cs.constData());
930 }
931 FcConfigSubstitute(config: nullptr, p: pattern, kind: FcMatchPattern);
932 FcDefaultSubstitute(pattern);
933
934 FcChar8 *familyAfterSubstitution = nullptr;
935 FcPatternGetString(p: pattern, FC_FAMILY, n: 0, s: &familyAfterSubstitution);
936 resolved = QString::fromUtf8(utf8: (const char *) familyAfterSubstitution);
937 FcPatternDestroy(p: pattern);
938
939 return resolved;
940}
941
942QFont QFontconfigDatabase::defaultFont() const
943{
944 // Hack to get system default language until FcGetDefaultLangs()
945 // is exported (https://bugs.freedesktop.org/show_bug.cgi?id=32853)
946 // or https://bugs.freedesktop.org/show_bug.cgi?id=35482 is fixed
947 FcPattern *dummy = FcPatternCreate();
948 FcDefaultSubstitute(pattern: dummy);
949 FcChar8 *lang = nullptr;
950 FcResult res = FcPatternGetString(p: dummy, FC_LANG, n: 0, s: &lang);
951
952 FcPattern *pattern = FcPatternCreate();
953 if (res == FcResultMatch) {
954 // Make defaultFont pattern matching locale language aware, because
955 // certain FC_LANG based custom rules may happen in FcConfigSubstitute()
956 FcPatternAddString(p: pattern, FC_LANG, s: lang);
957 }
958 FcConfigSubstitute(config: nullptr, p: pattern, kind: FcMatchPattern);
959 FcDefaultSubstitute(pattern);
960
961 FcChar8 *familyAfterSubstitution = nullptr;
962 FcPatternGetString(p: pattern, FC_FAMILY, n: 0, s: &familyAfterSubstitution);
963 QString resolved = QString::fromUtf8(utf8: (const char *) familyAfterSubstitution);
964 FcPatternDestroy(p: pattern);
965 FcPatternDestroy(p: dummy);
966
967 return QFont(resolved);
968}
969
970void QFontconfigDatabase::setupFontEngine(QFontEngineFT *engine, const QFontDef &fontDef) const
971{
972 bool antialias = !(fontDef.styleStrategy & QFont::NoAntialias);
973 bool forcedAntialiasSetting = !antialias || isDprScaling();
974
975 const QPlatformServices *services = QGuiApplicationPrivate::platformIntegration()->services();
976 bool preferXftConf = false;
977
978 if (services) {
979 const QList<QByteArray> desktopEnv = services->desktopEnvironment().split(sep: ':');
980 preferXftConf = !(desktopEnv.contains(t: "KDE") || desktopEnv.contains(t: "LXQT") || desktopEnv.contains(t: "UKUI"));
981 }
982
983 QFontEngine::GlyphFormat format;
984 // try and get the pattern
985 FcPattern *pattern = FcPatternCreate();
986 FcPattern *match = nullptr;
987
988 FcValue value;
989 value.type = FcTypeString;
990 QByteArray cs = fontDef.families.first().toUtf8();
991 value.u.s = (const FcChar8 *)cs.data();
992 FcPatternAdd(p: pattern,FC_FAMILY,value,append: true);
993
994 QFontEngine::FaceId fid = engine->faceId();
995
996 if (!fid.filename.isEmpty()) {
997 value.u.s = (const FcChar8 *)fid.filename.data();
998 FcPatternAdd(p: pattern,FC_FILE,value,append: true);
999
1000 value.type = FcTypeInteger;
1001 value.u.i = fid.index;
1002 FcPatternAdd(p: pattern,FC_INDEX,value,append: true);
1003 }
1004
1005 if (!qFuzzyIsNull(d: fontDef.pixelSize))
1006 FcPatternAddDouble(p: pattern, FC_PIXEL_SIZE, d: fontDef.pixelSize);
1007
1008 FcResult result;
1009
1010 FcConfigSubstitute(config: nullptr, p: pattern, kind: FcMatchPattern);
1011 FcDefaultSubstitute(pattern);
1012
1013#ifdef FC_VARIABLE
1014 if (!fid.filename.isEmpty()) {
1015 // FC_INDEX is ignored during processing in FcFontMatch.
1016 // So iterate FcPatterns directly and find it out.
1017 FcFontSet *fcsets[2], *fcfs;
1018
1019 fcsets[0] = FcConfigGetFonts(config: nullptr, set: FcSetSystem);
1020 fcsets[1] = FcConfigGetFonts(config: nullptr, set: FcSetApplication);
1021 for (int nset = 0; nset < 2; nset++) {
1022 fcfs = fcsets[nset];
1023 if (fcfs == nullptr)
1024 continue;
1025 for (int fnum = 0; fnum < fcfs->nfont; fnum++) {
1026 FcPattern *fcpat = fcfs->fonts[fnum];
1027 FcChar8 *fcfile;
1028 FcBool variable;
1029 double fcpixelsize;
1030 int fcindex;
1031
1032 // Skip the variable font itself, only to use the named instances and normal fonts here
1033 if (FcPatternGetBool(p: fcpat, FC_VARIABLE, n: 0, b: &variable) == FcResultMatch &&
1034 variable == FcTrue)
1035 continue;
1036
1037 if (!qFuzzyIsNull(d: fontDef.pixelSize)) {
1038 if (FcPatternGetDouble(p: fcpat, FC_PIXEL_SIZE, n: 0, d: &fcpixelsize) == FcResultMatch &&
1039 fontDef.pixelSize != fcpixelsize)
1040 continue;
1041 }
1042
1043 if (FcPatternGetString(p: fcpat, FC_FILE, n: 0, s: &fcfile) == FcResultMatch &&
1044 FcPatternGetInteger(p: fcpat, FC_INDEX, n: 0, i: &fcindex) == FcResultMatch) {
1045 QByteArray f = QByteArray::fromRawData(data: (const char *)fcfile,
1046 size: qstrlen(str: (const char *)fcfile));
1047 if (f == fid.filename && fcindex == fid.index) {
1048 // We found it.
1049 match = FcFontRenderPrepare(config: nullptr, pat: pattern, font: fcpat);
1050 goto bail;
1051 }
1052 }
1053 }
1054 }
1055 }
1056bail:
1057#endif
1058
1059 if (!match)
1060 match = FcFontMatch(config: nullptr, p: pattern, result: &result);
1061
1062 int xftAntialias = 0;
1063 if (!forcedAntialiasSetting) {
1064 void *antialiasResource =
1065 QGuiApplication::platformNativeInterface()->nativeResourceForScreen(resource: "antialiasingEnabled",
1066 screen: QGuiApplication::primaryScreen());
1067 xftAntialias = int(reinterpret_cast<qintptr>(antialiasResource));
1068 if ((preferXftConf || !match) && xftAntialias > 0) {
1069 antialias = xftAntialias - 1;
1070 forcedAntialiasSetting = true;
1071 }
1072 }
1073 if (match) {
1074 engine->setDefaultHintStyle(defaultHintStyleFromMatch(hintingPreference: (QFont::HintingPreference)fontDef.hintingPreference, match, preferXftConf));
1075
1076 FcBool fc_autohint;
1077 if (FcPatternGetBool(p: match, FC_AUTOHINT,n: 0, b: &fc_autohint) == FcResultMatch)
1078 engine->forceAutoHint = fc_autohint;
1079
1080#if defined(FT_LCD_FILTER_H)
1081 int lcdFilter;
1082 if (FcPatternGetInteger(p: match, FC_LCD_FILTER, n: 0, i: &lcdFilter) == FcResultMatch)
1083 engine->lcdFilterType = lcdFilter;
1084#endif
1085
1086 if (!forcedAntialiasSetting) {
1087 FcBool fc_antialias;
1088 if (FcPatternGetBool(p: match, FC_ANTIALIAS,n: 0, b: &fc_antialias) == FcResultMatch)
1089 antialias = fc_antialias;
1090 }
1091
1092 if (antialias) {
1093 QFontEngine::SubpixelAntialiasingType subpixelType = QFontEngine::Subpixel_None;
1094 if (!(fontDef.styleStrategy & QFont::NoSubpixelAntialias))
1095 subpixelType = subpixelTypeFromMatch(match, preferXftConf);
1096 engine->subpixelType = subpixelType;
1097 }
1098
1099 FcPatternDestroy(p: match);
1100 } else {
1101 void *hintStyleResource =
1102 QGuiApplication::platformNativeInterface()->nativeResourceForScreen(resource: "hintstyle",
1103 screen: QGuiApplication::primaryScreen());
1104 int xftHintStyle = int(reinterpret_cast<qintptr>(hintStyleResource));
1105 if (xftHintStyle > 0)
1106 engine->setDefaultHintStyle(QFontEngine::HintStyle(xftHintStyle - 1));
1107 if (antialias) {
1108 engine->subpixelType = QFontEngine::Subpixel_None;
1109 if (!(fontDef.styleStrategy & QFont::NoSubpixelAntialias)) {
1110 void *subpixelTypeResource =
1111 QGuiApplication::platformNativeInterface()->nativeResourceForScreen(resource: "subpixeltype",
1112 screen: QGuiApplication::primaryScreen());
1113 int xftSubpixelType = int(reinterpret_cast<qintptr>(subpixelTypeResource));
1114 if (xftSubpixelType > 1)
1115 engine->subpixelType = QFontEngine::SubpixelAntialiasingType(xftSubpixelType - 1);
1116 }
1117 }
1118 }
1119 if (antialias) {
1120 format = (engine->subpixelType == QFontEngine::Subpixel_None)
1121 ? QFontEngine::Format_A8
1122 : QFontEngine::Format_A32;
1123 } else {
1124 format = QFontEngine::Format_Mono;
1125 }
1126
1127 FcPatternDestroy(p: pattern);
1128
1129 engine->antialias = antialias;
1130 engine->defaultFormat = format;
1131 engine->glyphFormat = format;
1132}
1133
1134bool QFontconfigDatabase::supportsVariableApplicationFonts() const
1135{
1136#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900
1137 return true;
1138#else
1139 return false;
1140#endif
1141}
1142
1143QT_END_NAMESPACE
1144

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/gui/text/unix/qfontconfigdatabase.cpp