CoreText: Modernize style hint fallback lookup
The DefaultFontFallbacks.plist system file that we used for looking up style fallbacks does not exists in macOS 10.15, nor did it ever exists on iOS. Instead of relying on this file, we hard-code a set of default families, that we then look up the fallbacks for. The result of QFont::defaultFamily() on macOS is now: QFont::Helvetica --> "Helvetica" QFont::Times --> "Times New Roman" QFont::Courier --> "American Typewriter" QFont::OldEnglish --> "" QFont::System --> "Lucida Grande" QFont::AnyStyle --> "Lucida Grande" QFont::Cursive --> "Apple Chancery" QFont::Monospace --> "Menlo" QFont::Fantasy --> "Zapfino" And on iOS: QFont::Helvetica --> "Helvetica" QFont::Times --> "Times New Roman" QFont::Courier --> "American Typewriter" QFont::OldEnglish --> "" QFont::System --> "Helvetica" QFont::AnyStyle --> "Helvetica" QFont::Cursive --> "" QFont::Monospace --> "Menlo" QFont::Fantasy --> "Zapfino" Fixes: QTBUG-78240 Change-Id: Ie9bc13c9c1031d89f024199e4736a046c568a48d Reviewed-by: Simon Hausmann <simon.hausmann@qt.io> (cherry picked from commit 7f0faddf9fe25adc0a3e0827e21c030d6a7a1035) Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
parent
89bd5a7e73
commit
1e6a680f83
@ -100,20 +100,6 @@ static const char *languageForWritingSystem[] = {
|
|||||||
};
|
};
|
||||||
enum { LanguageCount = sizeof(languageForWritingSystem) / sizeof(const char *) };
|
enum { LanguageCount = sizeof(languageForWritingSystem) / sizeof(const char *) };
|
||||||
|
|
||||||
#ifdef Q_OS_OSX
|
|
||||||
static NSInteger languageMapSort(id obj1, id obj2, void *context)
|
|
||||||
{
|
|
||||||
NSArray<NSString *> *map1 = reinterpret_cast<NSArray<NSString *> *>(obj1);
|
|
||||||
NSArray<NSString *> *map2 = reinterpret_cast<NSArray<NSString *> *>(obj2);
|
|
||||||
NSArray<NSString *> *languages = reinterpret_cast<NSArray<NSString *> *>(context);
|
|
||||||
|
|
||||||
NSString *lang1 = [map1 objectAtIndex:0];
|
|
||||||
NSString *lang2 = [map2 objectAtIndex:0];
|
|
||||||
|
|
||||||
return [languages indexOfObject:lang1] - [languages indexOfObject:lang2];
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QCoreTextFontDatabase::QCoreTextFontDatabase()
|
QCoreTextFontDatabase::QCoreTextFontDatabase()
|
||||||
: m_hasPopulatedAliases(false)
|
: m_hasPopulatedAliases(false)
|
||||||
{
|
{
|
||||||
@ -406,143 +392,100 @@ template class QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>;
|
|||||||
template class QCoreTextFontDatabaseEngineFactory<QFontEngineFT>;
|
template class QCoreTextFontDatabaseEngineFactory<QFontEngineFT>;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QFont::StyleHint styleHintFromNSString(NSString *style)
|
QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family)
|
||||||
{
|
{
|
||||||
if ([style isEqual: @"sans-serif"])
|
if (family.isEmpty())
|
||||||
return QFont::SansSerif;
|
return QStringList();
|
||||||
else if ([style isEqual: @"monospace"])
|
|
||||||
return QFont::Monospace;
|
auto attributes = @{ id(kCTFontFamilyNameAttribute): family.toNSString() };
|
||||||
else if ([style isEqual: @"cursive"])
|
QCFType<CTFontDescriptorRef> fontDescriptor = CTFontDescriptorCreateWithAttributes(CFDictionaryRef(attributes));
|
||||||
return QFont::Cursive;
|
if (!fontDescriptor) {
|
||||||
else if ([style isEqual: @"serif"])
|
qWarning() << "Failed to create fallback font descriptor for" << family;
|
||||||
return QFont::Serif;
|
return QStringList();
|
||||||
else if ([style isEqual: @"fantasy"])
|
|
||||||
return QFont::Fantasy;
|
|
||||||
else // if ([style isEqual: @"default"])
|
|
||||||
return QFont::AnyStyle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_OSX
|
QCFType<CTFontRef> font = CTFontCreateWithFontDescriptor(fontDescriptor, 12.0, 0);
|
||||||
static QString familyNameFromPostScriptName(NSString *psName)
|
if (!font) {
|
||||||
{
|
qWarning() << "Failed to create fallback font for" << family;
|
||||||
QCFType<CTFontDescriptorRef> fontDescriptor = (CTFontDescriptorRef) CTFontDescriptorCreateWithNameAndSize((CFStringRef)psName, 12.0);
|
return QStringList();
|
||||||
QCFString familyName = (CFStringRef) CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontFamilyNameAttribute);
|
|
||||||
QString name = QString::fromCFString(familyName);
|
|
||||||
if (name.isEmpty())
|
|
||||||
qWarning() << "QCoreTextFontDatabase: Failed to resolve family name for PostScript name " << QString::fromCFString((CFStringRef)psName);
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
static void addExtraFallbacks(QStringList *fallbackList)
|
QCFType<CFArrayRef> cascadeList = CFArrayRef(CTFontCopyDefaultCascadeListForLanguages(font,
|
||||||
{
|
(CFArrayRef)[NSUserDefaults.standardUserDefaults stringArrayForKey:@"AppleLanguages"]));
|
||||||
#if defined(Q_OS_MACOS)
|
if (!cascadeList) {
|
||||||
// Since we are only returning a list of default fonts for the current language, we do not
|
qWarning() << "Failed to create fallback cascade list for" << family;
|
||||||
// cover all unicode completely. This was especially an issue for some of the common script
|
return QStringList();
|
||||||
// symbols such as mathematical symbols, currency or geometric shapes. To minimize the risk
|
}
|
||||||
// of missing glyphs, we add Arial Unicode MS as a final fail safe, since this covers most
|
|
||||||
// of Unicode 2.1.
|
QStringList fallbackList;
|
||||||
if (!fallbackList->contains(QStringLiteral("Arial Unicode MS")))
|
const int numCascades = CFArrayGetCount(cascadeList);
|
||||||
fallbackList->append(QStringLiteral("Arial Unicode MS"));
|
for (int i = 0; i < numCascades; ++i) {
|
||||||
// Since some symbols (specifically Braille) are not in Arial Unicode MS, we
|
CTFontDescriptorRef fontFallback = CTFontDescriptorRef(CFArrayGetValueAtIndex(cascadeList, i));
|
||||||
// add Apple Symbols to cover those too.
|
QCFString fallbackFamilyName = CFStringRef(CTFontDescriptorCopyAttribute(fontFallback, kCTFontFamilyNameAttribute));
|
||||||
if (!fallbackList->contains(QStringLiteral("Apple Symbols")))
|
fallbackList.append(QString::fromCFString(fallbackFamilyName));
|
||||||
fallbackList->append(QStringLiteral("Apple Symbols"));
|
}
|
||||||
#else
|
|
||||||
Q_UNUSED(fallbackList)
|
return fallbackList;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
|
QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(style);
|
Q_UNUSED(style);
|
||||||
Q_UNUSED(script);
|
|
||||||
|
|
||||||
QMacAutoReleasePool pool;
|
QMacAutoReleasePool pool;
|
||||||
|
|
||||||
static QHash<QString, QStringList> fallbackLists;
|
QStringList fallbackList = fallbacksForFamily(family);
|
||||||
|
|
||||||
if (!family.isEmpty()) {
|
if (fallbackList.isEmpty()) {
|
||||||
QCFType<CFMutableDictionaryRef> attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
// We were not able to find a fallback for the specific family,
|
||||||
CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, QCFString(family));
|
// or the family was empty, so we fall back to the style hint.
|
||||||
if (QCFType<CTFontDescriptorRef> fontDescriptor = CTFontDescriptorCreateWithAttributes(attributes)) {
|
QString styleFamily = [styleHint]{
|
||||||
if (QCFType<CTFontRef> font = CTFontCreateWithFontDescriptor(fontDescriptor, 12.0, 0)) {
|
switch (styleHint) {
|
||||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
case QFont::SansSerif: return QStringLiteral("Helvetica");
|
||||||
NSArray *languages = [defaults stringArrayForKey: @"AppleLanguages"];
|
case QFont::Serif: return QStringLiteral("Times New Roman");
|
||||||
|
case QFont::Monospace: return QStringLiteral("Menlo");
|
||||||
QCFType<CFArrayRef> cascadeList = (CFArrayRef) CTFontCopyDefaultCascadeListForLanguages(font, (CFArrayRef) languages);
|
#ifdef Q_OS_MACOS
|
||||||
if (cascadeList) {
|
case QFont::Cursive: return QStringLiteral("Apple Chancery");
|
||||||
QStringList fallbackList;
|
#endif
|
||||||
const int numCascades = CFArrayGetCount(cascadeList);
|
case QFont::Fantasy: return QStringLiteral("Zapfino");
|
||||||
for (int i = 0; i < numCascades; ++i) {
|
case QFont::TypeWriter: return QStringLiteral("American Typewriter");
|
||||||
CTFontDescriptorRef fontFallback = (CTFontDescriptorRef) CFArrayGetValueAtIndex(cascadeList, i);
|
case QFont::AnyStyle: Q_FALLTHROUGH();
|
||||||
QCFString fallbackFamilyName = (CFStringRef) CTFontDescriptorCopyAttribute(fontFallback, kCTFontFamilyNameAttribute);
|
case QFont::System: {
|
||||||
fallbackList.append(QString::fromCFString(fallbackFamilyName));
|
QCFType<CTFontRef> font = CTFontCreateUIFontForLanguage(kCTFontUIFontSystem, 12.0, NULL);
|
||||||
|
return static_cast<QString>(QCFString(CTFontCopyFullName(font)));
|
||||||
|
}
|
||||||
|
default: return QString(); // No matching font on this platform
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
if (!styleFamily.isEmpty()) {
|
||||||
|
fallbackList = fallbacksForFamily(styleFamily);
|
||||||
|
if (!fallbackList.contains(styleFamily))
|
||||||
|
fallbackList.prepend(styleFamily);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addExtraFallbacks(&fallbackList);
|
if (fallbackList.isEmpty())
|
||||||
|
return fallbackList;
|
||||||
|
|
||||||
|
#if defined(Q_OS_MACOS)
|
||||||
|
// Since we are only returning a list of default fonts for the current language, we do not
|
||||||
|
// cover all Unicode completely. This was especially an issue for some of the common script
|
||||||
|
// symbols such as mathematical symbols, currency or geometric shapes. To minimize the risk
|
||||||
|
// of missing glyphs, we add Arial Unicode MS as a final fail safe, since this covers most
|
||||||
|
// of Unicode 2.1.
|
||||||
|
if (!fallbackList.contains(QStringLiteral("Arial Unicode MS")))
|
||||||
|
fallbackList.append(QStringLiteral("Arial Unicode MS"));
|
||||||
|
// Since some symbols (specifically Braille) are not in Arial Unicode MS, we
|
||||||
|
// add Apple Symbols to cover those too.
|
||||||
|
if (!fallbackList.contains(QStringLiteral("Apple Symbols")))
|
||||||
|
fallbackList.append(QStringLiteral("Apple Symbols"));
|
||||||
|
#endif
|
||||||
|
|
||||||
extern QStringList qt_sort_families_by_writing_system(QChar::Script, const QStringList &);
|
extern QStringList qt_sort_families_by_writing_system(QChar::Script, const QStringList &);
|
||||||
fallbackList = qt_sort_families_by_writing_system(script, fallbackList);
|
fallbackList = qt_sort_families_by_writing_system(script, fallbackList);
|
||||||
|
|
||||||
return fallbackList;
|
return fallbackList;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We were not able to find a fallback for the specific family,
|
|
||||||
// so we fall back to the stylehint.
|
|
||||||
|
|
||||||
static const QString styleLookupKey = QString::fromLatin1(".QFontStyleHint_%1");
|
|
||||||
|
|
||||||
static bool didPopulateStyleFallbacks = false;
|
|
||||||
if (!didPopulateStyleFallbacks) {
|
|
||||||
#if defined(Q_OS_MACX)
|
|
||||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
||||||
NSArray<NSString *> *languages = [defaults stringArrayForKey:@"AppleLanguages"];
|
|
||||||
|
|
||||||
NSDictionary<NSString *, id> *fallbackDict = [NSDictionary<NSString *, id> dictionaryWithContentsOfFile:@"/System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreText.framework/Resources/DefaultFontFallbacks.plist"];
|
|
||||||
|
|
||||||
for (NSString *style in [fallbackDict allKeys]) {
|
|
||||||
NSArray *list = [fallbackDict valueForKey:style];
|
|
||||||
QFont::StyleHint fallbackStyleHint = styleHintFromNSString(style);
|
|
||||||
QStringList fallbackList;
|
|
||||||
for (id item in list) {
|
|
||||||
// sort the array based on system language preferences
|
|
||||||
if ([item isKindOfClass:[NSArray class]]) {
|
|
||||||
NSArray *langs = [reinterpret_cast<NSArray *>(item)
|
|
||||||
sortedArrayUsingFunction:languageMapSort context:languages];
|
|
||||||
for (NSArray<NSString *> *map in langs)
|
|
||||||
fallbackList.append(familyNameFromPostScriptName([map objectAtIndex:1]));
|
|
||||||
}
|
|
||||||
else if ([item isKindOfClass: [NSString class]])
|
|
||||||
fallbackList.append(familyNameFromPostScriptName(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
fallbackList.append(QLatin1String("Apple Color Emoji"));
|
|
||||||
|
|
||||||
addExtraFallbacks(&fallbackList);
|
|
||||||
fallbackLists[styleLookupKey.arg(fallbackStyleHint)] = fallbackList;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
QStringList staticFallbackList;
|
|
||||||
staticFallbackList << QString::fromLatin1("Helvetica,Apple Color Emoji,Geeza Pro,Arial Hebrew,Thonburi,Kailasa"
|
|
||||||
"Hiragino Kaku Gothic ProN,.Heiti J,Apple SD Gothic Neo,.Heiti K,Heiti SC,Heiti TC"
|
|
||||||
"Bangla Sangam MN,Devanagari Sangam MN,Gujarati Sangam MN,Gurmukhi MN,Kannada Sangam MN"
|
|
||||||
"Malayalam Sangam MN,Oriya Sangam MN,Sinhala Sangam MN,Tamil Sangam MN,Telugu Sangam MN"
|
|
||||||
"Euphemia UCAS,.PhoneFallback").split(QLatin1String(","));
|
|
||||||
|
|
||||||
for (int i = QFont::Helvetica; i <= QFont::Fantasy; ++i)
|
|
||||||
fallbackLists[styleLookupKey.arg(i)] = staticFallbackList;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
didPopulateStyleFallbacks = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_ASSERT(!fallbackLists.isEmpty());
|
|
||||||
return fallbackLists[styleLookupKey.arg(styleHint)];
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName)
|
QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName)
|
||||||
{
|
{
|
||||||
|
@ -92,6 +92,7 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void populateFromDescriptor(CTFontDescriptorRef font, const QString &familyName = QString());
|
void populateFromDescriptor(CTFontDescriptorRef font, const QString &familyName = QString());
|
||||||
|
static QStringList fallbacksForFamily(const QString &family);
|
||||||
|
|
||||||
mutable QString defaultFontName;
|
mutable QString defaultFontName;
|
||||||
|
|
||||||
|
@ -515,10 +515,10 @@ void tst_QFont::defaultFamily_data()
|
|||||||
QTest::addColumn<QStringList>("acceptableFamilies");
|
QTest::addColumn<QStringList>("acceptableFamilies");
|
||||||
|
|
||||||
QTest::newRow("serif") << QFont::Serif << (QStringList() << "Times New Roman" << "Times" << "Droid Serif" << getPlatformGenericFont("serif").split(","));
|
QTest::newRow("serif") << QFont::Serif << (QStringList() << "Times New Roman" << "Times" << "Droid Serif" << getPlatformGenericFont("serif").split(","));
|
||||||
QTest::newRow("monospace") << QFont::Monospace << (QStringList() << "Courier New" << "Monaco" << "Droid Sans Mono" << getPlatformGenericFont("monospace").split(","));
|
QTest::newRow("monospace") << QFont::Monospace << (QStringList() << "Courier New" << "Monaco" << "Menlo" << "Droid Sans Mono" << getPlatformGenericFont("monospace").split(","));
|
||||||
QTest::newRow("cursive") << QFont::Cursive << (QStringList() << "Comic Sans MS" << "Apple Chancery" << "Roboto" << "Droid Sans" << getPlatformGenericFont("cursive").split(","));
|
QTest::newRow("cursive") << QFont::Cursive << (QStringList() << "Comic Sans MS" << "Apple Chancery" << "Roboto" << "Droid Sans" << getPlatformGenericFont("cursive").split(","));
|
||||||
QTest::newRow("fantasy") << QFont::Fantasy << (QStringList() << "Impact" << "Zapfino" << "Roboto" << "Droid Sans" << getPlatformGenericFont("fantasy").split(","));
|
QTest::newRow("fantasy") << QFont::Fantasy << (QStringList() << "Impact" << "Zapfino" << "Roboto" << "Droid Sans" << getPlatformGenericFont("fantasy").split(","));
|
||||||
QTest::newRow("sans-serif") << QFont::SansSerif << (QStringList() << "Arial" << "Lucida Grande" << "Roboto" << "Droid Sans" << "Segoe UI" << getPlatformGenericFont("sans-serif").split(","));
|
QTest::newRow("sans-serif") << QFont::SansSerif << (QStringList() << "Arial" << "Lucida Grande" << "Helvetica" << "Roboto" << "Droid Sans" << "Segoe UI" << getPlatformGenericFont("sans-serif").split(","));
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_QFont::defaultFamily()
|
void tst_QFont::defaultFamily()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user