Fix application font population on OS X

This patch fixes two issues that prevented the application font related tests
of tst_QFontDatabase from passing:

* The code for creating the font descriptor after the registration of an app font
  with file name using CTFontDescriptorCreateWithAttributes must create a dictionary
  with kCTFontURLAttribute as key and the CFURLRef pointing to the on-disk file as
  value. Unfortunately the code mixed up keys and values in the dictionary.

* Registration of app fonts within QFontDatabase itself on Windows and Fontconfig
  platforms works by QFontDatabase calling addApplicationFont on the platform db
  after calling populateFontDatabase(). It assumes that addApplicationFont on the
  platform db is capable of registering the font right away. This part was also
  missing from the Mac implementation and this patch implements it by moving the
  common registration code from a CTFontDescriptorRef out into a separate method,
  called from populateFontDatabase() as well as addApplicationFont().

Task-number: QTBUG-23062

Change-Id: Ide5e6bf277d99f3cab50ee0d4631cc3fba6d0d45
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@digia.com>
bb10
Simon Hausmann 2013-07-17 14:53:27 +02:00 committed by The Qt Project
parent c6ccd27818
commit 3745b0ca12
3 changed files with 133 additions and 78 deletions

View File

@ -176,6 +176,10 @@ QCoreTextFontDatabase::~QCoreTextFontDatabase()
void QCoreTextFontDatabase::populateFontDatabase()
{
// The caller (QFontDB) expects the db to be populate only with system fonts, so we need
// to make sure that any previously registered app fonts become invisible.
removeApplicationFonts();
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
QCFType<CTFontCollectionRef> collection = CTFontCollectionCreateFromAvailableFonts(0);
@ -186,84 +190,89 @@ void QCoreTextFontDatabase::populateFontDatabase()
if (! fonts)
return;
QString foundryName = QLatin1String("CoreText");
const int numFonts = CFArrayGetCount(fonts);
for (int i = 0; i < numFonts; ++i) {
CTFontDescriptorRef font = (CTFontDescriptorRef) CFArrayGetValueAtIndex(fonts, i);
QCFString familyName = (CFStringRef) CTFontDescriptorCopyLocalizedAttribute(font, kCTFontFamilyNameAttribute, NULL);
QCFString styleName = (CFStringRef)CTFontDescriptorCopyLocalizedAttribute(font, kCTFontStyleNameAttribute, NULL);
QCFType<CFDictionaryRef> styles = (CFDictionaryRef) CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute);
QFont::Weight weight = QFont::Normal;
QFont::Style style = QFont::StyleNormal;
QFont::Stretch stretch = QFont::Unstretched;
bool fixedPitch = false;
if (styles) {
if (CFNumberRef weightValue = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontWeightTrait)) {
Q_ASSERT(CFNumberIsFloatType(weightValue));
double d;
if (CFNumberGetValue(weightValue, kCFNumberDoubleType, &d))
weight = (d > 0.0) ? QFont::Bold : QFont::Normal;
}
if (CFNumberRef italic = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontSlantTrait)) {
Q_ASSERT(CFNumberIsFloatType(italic));
double d;
if (CFNumberGetValue(italic, kCFNumberDoubleType, &d)) {
if (d > 0.0)
style = QFont::StyleItalic;
}
}
if (CFNumberRef symbolic = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontSymbolicTrait)) {
int d;
if (CFNumberGetValue(symbolic, kCFNumberSInt32Type, &d)) {
if (d & kCTFontMonoSpaceTrait)
fixedPitch = true;
if (d & kCTFontExpandedTrait)
stretch = QFont::Expanded;
else if (d & kCTFontCondensedTrait)
stretch = QFont::Condensed;
}
}
}
int pixelSize = 0;
if (QCFType<CFNumberRef> size = (CFNumberRef) CTFontDescriptorCopyAttribute(font, kCTFontSizeAttribute)) {
if (CFNumberIsFloatType(size)) {
double d;
CFNumberGetValue(size, kCFNumberDoubleType, &d);
pixelSize = d;
} else {
CFNumberGetValue(size, kCFNumberIntType, &pixelSize);
}
}
QSupportedWritingSystems writingSystems;
if (QCFType<CFArrayRef> languages = (CFArrayRef) CTFontDescriptorCopyAttribute(font, kCTFontLanguagesAttribute)) {
CFIndex length = CFArrayGetCount(languages);
for (int i = 1; i < LanguageCount; ++i) {
if (!languageForWritingSystem[i])
continue;
QCFString lang = CFStringCreateWithCString(NULL, languageForWritingSystem[i], kCFStringEncodingASCII);
if (CFArrayContainsValue(languages, CFRangeMake(0, length), lang))
writingSystems.setSupported(QFontDatabase::WritingSystem(i));
}
}
CFRetain(font);
QPlatformFontDatabase::registerFont(familyName, styleName, foundryName, weight, style, stretch,
true /* antialiased */, true /* scalable */,
pixelSize, fixedPitch, writingSystems, (void *) font);
// We need to map back and forth between PostScript-names and family-names for fallback list construction
CFStringRef psName = (CFStringRef) CTFontDescriptorCopyAttribute(font, kCTFontNameAttribute);
psNameToFamily[QCFString::toQString((NSString *) psName)] = familyName;
familyNameToPsName[familyName] = QCFString::toQString((NSString *) psName);
CFRelease(psName);
populateFromDescriptor(font);
}
[pool release];
}
void QCoreTextFontDatabase::populateFromDescriptor(CTFontDescriptorRef font)
{
QString foundryName = QStringLiteral("CoreText");
QCFString familyName = (CFStringRef) CTFontDescriptorCopyLocalizedAttribute(font, kCTFontFamilyNameAttribute, NULL);
QCFString styleName = (CFStringRef)CTFontDescriptorCopyLocalizedAttribute(font, kCTFontStyleNameAttribute, NULL);
QCFType<CFDictionaryRef> styles = (CFDictionaryRef) CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute);
QFont::Weight weight = QFont::Normal;
QFont::Style style = QFont::StyleNormal;
QFont::Stretch stretch = QFont::Unstretched;
bool fixedPitch = false;
if (styles) {
if (CFNumberRef weightValue = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontWeightTrait)) {
Q_ASSERT(CFNumberIsFloatType(weightValue));
double d;
if (CFNumberGetValue(weightValue, kCFNumberDoubleType, &d))
weight = (d > 0.0) ? QFont::Bold : QFont::Normal;
}
if (CFNumberRef italic = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontSlantTrait)) {
Q_ASSERT(CFNumberIsFloatType(italic));
double d;
if (CFNumberGetValue(italic, kCFNumberDoubleType, &d)) {
if (d > 0.0)
style = QFont::StyleItalic;
}
}
if (CFNumberRef symbolic = (CFNumberRef) CFDictionaryGetValue(styles, kCTFontSymbolicTrait)) {
int d;
if (CFNumberGetValue(symbolic, kCFNumberSInt32Type, &d)) {
if (d & kCTFontMonoSpaceTrait)
fixedPitch = true;
if (d & kCTFontExpandedTrait)
stretch = QFont::Expanded;
else if (d & kCTFontCondensedTrait)
stretch = QFont::Condensed;
}
}
}
int pixelSize = 0;
if (QCFType<CFNumberRef> size = (CFNumberRef) CTFontDescriptorCopyAttribute(font, kCTFontSizeAttribute)) {
if (CFNumberIsFloatType(size)) {
double d;
CFNumberGetValue(size, kCFNumberDoubleType, &d);
pixelSize = d;
} else {
CFNumberGetValue(size, kCFNumberIntType, &pixelSize);
}
}
QSupportedWritingSystems writingSystems;
if (QCFType<CFArrayRef> languages = (CFArrayRef) CTFontDescriptorCopyAttribute(font, kCTFontLanguagesAttribute)) {
CFIndex length = CFArrayGetCount(languages);
for (int i = 1; i < LanguageCount; ++i) {
if (!languageForWritingSystem[i])
continue;
QCFString lang = CFStringCreateWithCString(NULL, languageForWritingSystem[i], kCFStringEncodingASCII);
if (CFArrayContainsValue(languages, CFRangeMake(0, length), lang))
writingSystems.setSupported(QFontDatabase::WritingSystem(i));
}
}
CFRetain(font);
QPlatformFontDatabase::registerFont(familyName, styleName, foundryName, weight, style, stretch,
true /* antialiased */, true /* scalable */,
pixelSize, fixedPitch, writingSystems, (void *) font);
// We need to map back and forth between PostScript-names and family-names for fallback list construction
CFStringRef psName = (CFStringRef) CTFontDescriptorCopyAttribute(font, kCTFontNameAttribute);
psNameToFamily[QCFString::toQString((NSString *) psName)] = familyName;
familyNameToPsName[familyName] = QCFString::toQString((NSString *) psName);
CFRelease(psName);
}
void QCoreTextFontDatabase::releaseHandle(void *handle)
{
CFRelease(CTFontDescriptorRef(handle));
@ -473,6 +482,7 @@ QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData
bool success = CTFontManagerRegisterGraphicsFont(cgFont, &error);
if (success) {
font = CTFontCreateWithGraphicsFont(cgFont, 0.0, NULL, NULL);
m_applicationGraphicsFonts.append(QCFType<CGFontRef>::constructFromGet(cgFont));
} else {
NSLog(@"Unable to register font: %@", error);
CFRelease(error);
@ -484,12 +494,13 @@ QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData
QCFType<CFURLRef> fontURL = CFURLCreateWithFileSystemPath(NULL, QCFString(fileName), kCFURLPOSIXPathStyle, false);
bool success = CTFontManagerRegisterFontsForURL(fontURL, kCTFontManagerScopeProcess, &error);
if (success) {
const void *keys[] = { fontURL };
const void *values[] = { kCTFontURLAttribute };
const void *keys[] = { kCTFontURLAttribute };
const void *values[] = { fontURL };
QCFType<CFDictionaryRef> attributes = CFDictionaryCreate(NULL, keys, values, 1,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
QCFType<CTFontDescriptorRef> descriptor = CTFontDescriptorCreateWithAttributes(attributes);
font = CTFontCreateWithFontDescriptor(descriptor, 0.0, NULL);
m_applicationURLFonts.append(QCFType<CFURLRef>::constructFromGet(fontURL));
} else {
NSLog(@"Unable to register font: %@", error);
CFRelease(error);
@ -499,6 +510,10 @@ QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData
if (font) {
QStringList families;
families.append(QCFString(CTFontCopyFamilyName(font)));
QCFType<CTFontDescriptorRef> descriptor = CTFontCopyFontDescriptor(font);
populateFromDescriptor(descriptor);
CFRelease(font);
return families;
}
@ -534,9 +549,12 @@ QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData
QStringList families;
for (int i = 0; i < containedFonts.size(); ++i) {
QCFType<CTFontRef> font = CTFontCreateWithPlatformFont(containedFonts[i], 12.0, NULL, NULL);
QCFType<CTFontDescriptorRef> descriptor = CTFontCopyFontDescriptor(font);
populateFromDescriptor(descriptor);
families.append(QCFString(CTFontCopyFamilyName(font)));
}
m_applicationFonts.append(fontContainer);
return families;
}
#endif
@ -569,5 +587,28 @@ QList<int> QCoreTextFontDatabase::standardSizes() const
return ret;
}
void QCoreTextFontDatabase::removeApplicationFonts()
{
#ifdef Q_OS_MACX
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
if (QSysInfo::QSysInfo::MacintoshVersion >= QSysInfo::MV_10_8) {
CFErrorRef error;
for (int i = 0; i < m_applicationGraphicsFonts.count(); ++i)
CTFontManagerUnregisterGraphicsFont(m_applicationGraphicsFonts[i], &error);
m_applicationGraphicsFonts.clear();
for (int i = 0; i < m_applicationURLFonts.count(); ++i)
CTFontManagerUnregisterFontsForURL(m_applicationURLFonts[i], kCTFontManagerScopeProcess, &error);
m_applicationURLFonts.clear();
}
#else
for (int i = 0; i < m_applicationFonts.count(); ++i)
ATSFontDeactivate(m_applicationFonts[i], 0, kATSOptionFlagsDoNotNotify);
m_applicationFonts.clear();
ATSFontNotify(kATSFontNotifyActionFontsChanged, 0);
#endif
#endif
}
QT_END_NAMESPACE

View File

@ -43,6 +43,14 @@
#define QCORETEXTFONTDATABASE_H
#include <qpa/qplatformfontdatabase.h>
#include <private/qcore_mac_p.h>
#ifndef Q_OS_IOS
#include <ApplicationServices/ApplicationServices.h>
#else
#include <CoreText/CoreText.h>
#include <CoreGraphics/CoreGraphics.h>
#endif
QT_BEGIN_NAMESPACE
@ -63,9 +71,21 @@ public:
QList<int> standardSizes() const;
private:
void populateFromDescriptor(CTFontDescriptorRef font);
mutable QString defaultFontName;
mutable QHash<QString, QString> psNameToFamily;
mutable QHash<QString, QString> familyNameToPsName;
void removeApplicationFonts();
#ifdef Q_OS_MACX
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
QVector<QCFType<CGFontRef> > m_applicationGraphicsFonts;
QVector<QCFType<CFURLRef> > m_applicationURLFonts;
#else
QVector<ATSFontContainerRef> m_applicationFonts;
#endif
#endif
};
QT_END_NAMESPACE

View File

@ -253,11 +253,8 @@ void tst_QFontDatabase::addAppFont()
return;
#endif
QCOMPARE(fontDbChangedSpy.count(), 1);
// addApplicationFont is supported on Mac, don't skip the test if it breaks.
#ifndef Q_OS_MAC
if (id == -1)
QSKIP("Skip the test since app fonts are not supported on this system");
#endif
const QStringList addedFamilies = QFontDatabase::applicationFontFamilies(id);
QVERIFY(!addedFamilies.isEmpty());
@ -272,9 +269,6 @@ void tst_QFontDatabase::addAppFont()
QVERIFY(QFontDatabase::removeApplicationFont(id));
QCOMPARE(fontDbChangedSpy.count(), 2);
#ifdef Q_OS_MAC
QEXPECT_FAIL("font file", "QTBUG-23062", Continue);
#endif
QCOMPARE(db.families(), oldFamilies);
}