CoreText: Populate all variants of theme/system fonts

We populate the various QPlatformTheme::Fonts by asking the system for
the preferred font for each use-case, but that just gives us a single
font descriptor with a single style and weight. If the user then tweaks
the font by e.g. making it bold or italic, our font database will not
have any knowledge of these variants, and will fall back to another
font.

To fix this we ask CoreText for all variants of each of the theme fonts,
so that each of the theme font families are fully populated.

The preferred way to do this on macOS 10.15/iOS 13 and above is by using
the font's UI design trait. This avoids asking CoreText for private
fonts by family name directly, which is not supported and may result
in giving us Times or another fallback font instead.

Pick-to: 6.2 6.3
Change-Id: I4aa21624df62ac09a92d7ae0bc2cdde4f6ad6e5f
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
Tor Arne Vestbø 2022-03-23 14:22:22 +01:00
parent a068f2d7c9
commit c7280b3362
3 changed files with 63 additions and 13 deletions

View File

@ -192,6 +192,7 @@ public:
EditorFont,
NFonts
};
Q_ENUM(Font)
enum StandardPixmap { // Keep in sync with QStyle::StandardPixmap
TitleBarMenuButton,

View File

@ -110,9 +110,6 @@ QCoreTextFontDatabase::QCoreTextFontDatabase()
QCoreTextFontDatabase::~QCoreTextFontDatabase()
{
qDeleteAll(m_themeFonts);
for (CTFontDescriptorRef ref : qAsConst(m_systemFontDescriptors))
CFRelease(ref);
}
void QCoreTextFontDatabase::populateFontDatabase()
@ -130,8 +127,13 @@ void QCoreTextFontDatabase::populateFontDatabase()
populateThemeFonts();
for (CTFontDescriptorRef fontDesc : m_systemFontDescriptors)
populateFromDescriptor(fontDesc);
for (auto familyName : m_systemFontDescriptors.keys()) {
for (auto fontDescriptor : m_systemFontDescriptors.value(familyName))
populateFromDescriptor(fontDescriptor, familyName);
}
// The font database now has a reference to the original descriptors
m_systemFontDescriptors.clear();
qCDebug(lcQpaFonts) << "Populating system descriptors took" << elapsed.restart() << "ms";
@ -202,6 +204,8 @@ CTFontDescriptorRef descriptorForFamily(const char *familyName)
void QCoreTextFontDatabase::populateFamily(const QString &familyName)
{
qCDebug(lcQpaFonts) << "Populating family" << familyName;
// A single family might match several different fonts with different styles.
// We need to add them all so that the font database has the full picture,
// as once a family has been populated we will not populate it again.
@ -749,6 +753,8 @@ static CTFontDescriptorRef fontDescriptorFromTheme(QPlatformTheme::Font f)
void QCoreTextFontDatabase::populateThemeFonts()
{
QMacAutoReleasePool pool;
if (!m_themeFonts.isEmpty())
return;
@ -760,7 +766,7 @@ void QCoreTextFontDatabase::populateThemeFonts()
for (long f = QPlatformTheme::SystemFont; f < QPlatformTheme::NFonts; f++) {
QPlatformTheme::Font themeFont = static_cast<QPlatformTheme::Font>(f);
CTFontDescriptorRef fontDescriptor = fontDescriptorFromTheme(themeFont);
QCFType<CTFontDescriptorRef> fontDescriptor = fontDescriptorFromTheme(themeFont);
FontDescription fd;
getFontDescription(fontDescriptor, &fd);
@ -770,10 +776,54 @@ void QCoreTextFontDatabase::populateThemeFonts()
// would result in the font database having > 0 families, which would result in
// skipping the initialization and population of all other font families. Instead
// we store the descriptors for later and populate them during populateFontDatabase().
if (!m_systemFontDescriptors.contains(fontDescriptor))
m_systemFontDescriptors.insert(fontDescriptor);
else
CFRelease(fontDescriptor);
bool haveRegisteredFamily = m_systemFontDescriptors.contains(fd.familyName);
qCDebug(lcQpaFonts) << "Got" << (haveRegisteredFamily ? "already registered" : "unseen")
<< "family" << fd.familyName << "for" << themeFont;
if (!haveRegisteredFamily) {
// We need to register all weights and variants of the theme font,
// as the user might tweak the returned QFont before use.
QList<QCFType<CTFontDescriptorRef>> themeFontVariants;
auto addFontVariants = [&](CTFontDescriptorRef descriptor) {
QCFType<CFArrayRef> matchingDescriptors = CTFontDescriptorCreateMatchingFontDescriptors(descriptor, nullptr);
const int matchingDescriptorsCount = CFArrayGetCount(matchingDescriptors);
qCDebug(lcQpaFonts) << "Enumerating font variants based on" << id(descriptor)
<< "resulted in" << matchingDescriptorsCount << "matching descriptors"
<< matchingDescriptors.as<NSArray*>();
for (int i = 0; i < matchingDescriptorsCount; ++i) {
auto matchingDescriptor = CTFontDescriptorRef(CFArrayGetValueAtIndex(matchingDescriptors, i));
themeFontVariants.append(QCFType<CTFontDescriptorRef>::constructFromGet(matchingDescriptor));
}
};
// Try populating the font variants based on its UI design trait, if available
if (@available(macos 10.15, ios 13.0, *)) {
auto fontTraits = QCFType<CFDictionaryRef>(CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontTraitsAttribute));
static const NSString *kUIFontDesignTrait = @"NSCTFontUIFontDesignTrait";
if (id uiFontDesignTrait = fontTraits.as<NSDictionary*>()[kUIFontDesignTrait]) {
QCFType<CTFontDescriptorRef> designTraitDescriptor = CTFontDescriptorCreateWithAttributes(
CFDictionaryRef(@{ (id)kCTFontTraitsAttribute: @{ kUIFontDesignTrait: uiFontDesignTrait }
}));
addFontVariants(designTraitDescriptor);
}
}
if (themeFontVariants.isEmpty()) {
// Fall back to populating variants based on the family name alone
QCFType<CTFontDescriptorRef> familyDescriptor = descriptorForFamily(fd.familyName);
addFontVariants(familyDescriptor);
}
if (themeFontVariants.isEmpty()) {
qCDebug(lcQpaFonts) << "No theme font variants found, falling back to single variant descriptor";
themeFontVariants.append(fontDescriptor);
}
m_systemFontDescriptors.insert(fd.familyName, themeFontVariants);
}
QFont *font = new QFont(fd.familyName, fd.pointSize, fd.weight, fd.style == QFont::StyleItalic);
m_themeFonts.insert(themeFont, font);

View File

@ -86,9 +86,6 @@ public:
// For iOS and macOS platform themes
QFont *themeFont(QPlatformTheme::Font) const;
protected:
mutable QSet<CTFontDescriptorRef> m_systemFontDescriptors;
private:
void populateThemeFonts();
void populateFromDescriptor(CTFontDescriptorRef font, const QString &familyName = QString(), QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr);
@ -97,6 +94,8 @@ private:
mutable QString defaultFontName;
QHash<QPlatformTheme::Font, QFont *> m_themeFonts;
QHash<QString, QList<QCFType<CTFontDescriptorRef>>> m_systemFontDescriptors;
bool m_hasPopulatedAliases;
};