macOS: Don't marshal app font data via URL when using FreeType font engine

Font descriptors can have attached attributes of any kind, not just pre-
defined constants like kCTFontURLAttribute. We take advantage of this and
attach the font data that was passed into addApplicationFont() to the
font descriptor, so we can read it out directly when later creating an
engine for it. This removes the need to build up a URL to represent the
font data, which also didn't work for the memory-font use-case. The
FreeType font engine now passes the same tst_QFontDatabase tests on
macOS as the native CoreText font engine.

This also fixes the leak caused by CTFontCreateWithGraphicsFont never
releasing the graphics font, resulting in releaseFontData never being
called:

  http://stackoverflow.com/questions/40805382/

We're now cleaning up the font data in releaseHandle, based on the
attribute set on the font descriptor.

Change-Id: Iba15222ec919f989e29fd98b263d9fb182c4d710
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
Tor Arne Vestbø 2017-04-03 19:07:43 +02:00
parent 6ef07e0902
commit 31273f079e
2 changed files with 33 additions and 71 deletions

View File

@ -354,9 +354,22 @@ void QCoreTextFontDatabase::populateFromDescriptor(CTFontDescriptorRef font, con
fd.pixelSize, fd.fixedPitch, fd.writingSystems, (void *) font); fd.pixelSize, fd.fixedPitch, fd.writingSystems, (void *) font);
} }
static NSString * const kQtFontDataAttribute = @"QtFontDataAttribute";
template <typename T>
T *descriptorAttribute(CTFontDescriptorRef descriptor, CFStringRef name)
{
return [static_cast<T *>(CTFontDescriptorCopyAttribute(descriptor, name)) autorelease];
}
void QCoreTextFontDatabase::releaseHandle(void *handle) void QCoreTextFontDatabase::releaseHandle(void *handle)
{ {
CFRelease(CTFontDescriptorRef(handle)); CTFontDescriptorRef descriptor = static_cast<CTFontDescriptorRef>(handle);
if (NSValue *fontDataValue = descriptorAttribute<NSValue>(descriptor, (CFStringRef)kQtFontDataAttribute)) {
QByteArray *fontData = static_cast<QByteArray *>(fontDataValue.pointerValue);
delete fontData;
}
CFRelease(descriptor);
} }
extern CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef); extern CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef);
@ -391,30 +404,21 @@ QFontEngine *QCoreTextFontDatabaseEngineFactory<QFontEngineFT>::fontEngine(const
{ {
CTFontDescriptorRef descriptor = static_cast<CTFontDescriptorRef>(usrPtr); CTFontDescriptorRef descriptor = static_cast<CTFontDescriptorRef>(usrPtr);
QByteArray filename; if (NSURL *url = descriptorAttribute<NSURL>(descriptor, kCTFontURLAttribute)) {
if (NSURL *url = [static_cast<NSURL *>(CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute)) autorelease]) { Q_ASSERT(url.fileURL);
if ([url.scheme isEqual:@"qrc"])
filename = ":";
else if (!url.fileURL)
qWarning() << "QFontDatabase: Unknown scheme" << url.scheme << "in font descriptor URL";
filename += QString::fromNSString(url.path).toUtf8();
}
Q_ASSERT(!filename.isEmpty());
QFontEngine::FaceId faceId; QFontEngine::FaceId faceId;
faceId.filename = filename; faceId.filename = QString::fromNSString(url.path).toUtf8();
return QFontEngineFT::create(fontDef, faceId); return QFontEngineFT::create(fontDef, faceId);
} else if (NSValue *fontDataValue = descriptorAttribute<NSValue>(descriptor, (CFStringRef)kQtFontDataAttribute)) {
QByteArray *fontData = static_cast<QByteArray *>(fontDataValue.pointerValue);
return QFontEngineFT::create(*fontData, fontDef.pixelSize,
static_cast<QFont::HintingPreference>(fontDef.hintingPreference));
}
Q_UNREACHABLE();
} }
#endif #endif
static void releaseFontData(void* info, const void* data, size_t size)
{
Q_UNUSED(data);
Q_UNUSED(size);
delete (QByteArray*)info;
}
template <> template <>
QFontEngine *QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) QFontEngine *QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
{ {
@ -580,59 +584,20 @@ QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family, QFo
return fallbackLists[styleLookupKey.arg(styleHint)]; return fallbackLists[styleLookupKey.arg(styleHint)];
} }
template <>
CFArrayRef QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>::createDescriptorArrayForDescriptor(CTFontDescriptorRef descriptor, const QString &fileName)
{
Q_UNUSED(fileName)
CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(array, descriptor);
return array;
}
#ifndef QT_NO_FREETYPE
template <>
CFArrayRef QCoreTextFontDatabaseEngineFactory<QFontEngineFT>::createDescriptorArrayForDescriptor(CTFontDescriptorRef descriptor, const QString &fileName)
{
CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
// The physical font source URL (usually a local file or Qt resource) is only required for
// FreeType, when using non-system fonts, and needs some hackery to attach in a format
// agreeable to OSX.
if (!fileName.isEmpty()) {
QCFType<CFURLRef> fontURL;
if (fileName.startsWith(QLatin1String(":/"))) {
// QUrl::fromLocalFile() doesn't accept qrc pseudo-paths like ":/fonts/myfont.ttf".
// Therefore construct from QString with the qrc:// scheme -> "qrc:///fonts/myfont.ttf".
fontURL = QUrl(QStringLiteral("qrc://") + fileName.mid(1)).toCFURL();
} else {
// At this point we hope that filename is in a format that QUrl can handle.
fontURL = QUrl::fromLocalFile(fileName).toCFURL();
}
QCFType<CFMutableDictionaryRef> attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 1,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(attributes, kCTFontURLAttribute, fontURL);
descriptor = CTFontDescriptorCreateCopyWithAttributes(descriptor, attributes);
}
CFArrayAppendValue(array, descriptor);
return array;
}
#endif
QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName) QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName)
{ {
QCFType<CFArrayRef> fonts; QCFType<CFArrayRef> fonts;
if (!fontData.isEmpty()) { if (!fontData.isEmpty()) {
QByteArray* fontDataCopy = new QByteArray(fontData); QCFType<CFDataRef> fontDataReference = fontData.toRawCFData();
QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithData(fontDataCopy, if (QCFType<CTFontDescriptorRef> descriptor = CTFontManagerCreateFontDescriptorFromData(fontDataReference)) {
fontDataCopy->constData(), fontDataCopy->size(), releaseFontData); // There's no way to get the data back out of a font descriptor created with
if (QCFType<CGFontRef> cgFont = CGFontCreateWithDataProvider(dataProvider)) { // CTFontManagerCreateFontDescriptorFromData, so we attach the data manually.
QCFType<CTFontRef> ctFont = CTFontCreateWithGraphicsFont(cgFont, 0.0, NULL, NULL); NSDictionary *attributes = @{ kQtFontDataAttribute : [NSValue valueWithPointer:new QByteArray(fontData)] };
QCFType<CTFontDescriptorRef> descriptor = CTFontCopyFontDescriptor(ctFont); descriptor = CTFontDescriptorCreateCopyWithAttributes(descriptor, (CFDictionaryRef)attributes);
fonts = createDescriptorArrayForDescriptor(descriptor, fileName); CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(array, descriptor);
fonts = array;
} }
} else { } else {
QCFType<CFURLRef> fontURL = QUrl::fromLocalFile(fileName).toCFURL(); QCFType<CFURLRef> fontURL = QUrl::fromLocalFile(fileName).toCFURL();

View File

@ -91,7 +91,6 @@ public:
private: private:
void populateFromDescriptor(CTFontDescriptorRef font, const QString &familyName = QString()); void populateFromDescriptor(CTFontDescriptorRef font, const QString &familyName = QString());
virtual CFArrayRef createDescriptorArrayForDescriptor(CTFontDescriptorRef descriptor, const QString &fileName) = 0;
mutable QString defaultFontName; mutable QString defaultFontName;
@ -108,8 +107,6 @@ class QCoreTextFontDatabaseEngineFactory : public QCoreTextFontDatabase
public: public:
QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override; QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) override; QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) override;
protected:
CFArrayRef createDescriptorArrayForDescriptor(CTFontDescriptorRef descriptor, const QString &fileName) override;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE