OSX: Add initial FreeType support

This permits text rendering consistent with other FreeType enabled platforms,
like Windows and Linux.

Task-number: QTBUG-42839
Change-Id: I8c99bcaa3fb07c16e935a0c3705af467bc3da584
Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
This commit is contained in:
Mathias Hasselmann 2014-11-07 22:22:10 +01:00 committed by Andreas Hartmetz
parent 5551295028
commit bf013c9e74
7 changed files with 188 additions and 13 deletions

View File

@ -1120,6 +1120,15 @@ QT_BEGIN_INCLUDE_NAMESPACE
QT_END_INCLUDE_NAMESPACE
#if defined(Q_OS_OSX) && !defined(QT_NO_FREETYPE)
static const char *s_shapersForOsxFreeType[] =
{
"ot",
"fallback",
Q_NULLPTR
};
#endif
int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *string, int itemLength, QFontEngine *fontEngine, const QVector<uint> &itemBoundaries, bool kerningEnabled) const
{
uint glyphs_shaped = 0;
@ -1172,7 +1181,15 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st
};
const int num_features = 1;
bool shapedOk = hb_shape_full(hb_font, buffer, features, num_features, 0);
const char *const *shaper_list = Q_NULLPTR;
#if defined(Q_OS_OSX) && !defined(QT_NO_FREETYPE)
// What's behind QFontEngine::FaceData::user_data isn't compatible between CoreText and
// FreeType font engines - specifically functions in hb-coretext.cc would run into undefined
// behavior with data from the FreeType engine. The OpenType shaper works with that engine.
if (actualFontEngine->type() == QFontEngine::Freetype)
shaper_list = s_shapersForOsxFreeType;
#endif
bool shapedOk = hb_shape_full(hb_font, buffer, features, num_features, shaper_list);
if (Q_UNLIKELY(!shapedOk)) {
hb_buffer_destroy(buffer);
return 0;

View File

@ -1,6 +1,13 @@
HEADERS += $$PWD/qcoretextfontdatabase_p.h $$PWD/qfontengine_coretext_p.h
OBJECTIVE_SOURCES += $$PWD/qfontengine_coretext.mm $$PWD/qcoretextfontdatabase.mm
contains(QT_CONFIG, freetype) {
include($$QT_SOURCE_TREE/src/3rdparty/freetype_dependency.pri)
HEADERS += $$QT_SOURCE_TREE/src/gui/text/qfontengine_ft_p.h
SOURCES += $$QT_SOURCE_TREE/src/gui/text/qfontengine_ft.cpp
CONFIG += opentype
}
ios: \
# On iOS CoreText and CoreGraphics are stand-alone frameworks
LIBS_PRIVATE += -framework CoreText -framework CoreGraphics

View File

@ -33,6 +33,8 @@
#include "qglobal.h"
#include <sys/param.h>
#if defined(Q_OS_MACX)
#import <Cocoa/Cocoa.h>
#import <IOKit/graphics/IOGraphicsLib.h>
@ -45,6 +47,9 @@
#include <QtCore/QSettings>
#include <QtGui/QGuiApplication>
#include <QtCore/QtEndian>
#ifndef QT_NO_FREETYPE
#include <QtGui/private/qfontengine_ft_p.h>
#endif
QT_BEGIN_NAMESPACE
@ -102,8 +107,12 @@ static NSInteger languageMapSort(id obj1, id obj2, void *context)
}
#endif
QCoreTextFontDatabase::QCoreTextFontDatabase()
QCoreTextFontDatabase::QCoreTextFontDatabase(bool useFreeType)
#ifndef QT_NO_FREETYPE
: m_useFreeType(useFreeType)
#endif
{
Q_UNUSED(useFreeType)
#ifdef Q_OS_MACX
QSettings appleSettings(QLatin1String("apple.com"));
QVariant appleValue = appleSettings.value(QLatin1String("AppleAntiAliasingThreshold"));
@ -348,10 +357,48 @@ void QCoreTextFontDatabase::releaseHandle(void *handle)
CFRelease(CTFontDescriptorRef(handle));
}
#ifndef QT_NO_FREETYPE
static QByteArray filenameForCFUrl(CFURLRef url)
{
// The on-stack buffer prevents that a QByteArray allocated for the worst case (MAXPATHLEN)
// stays around for the lifetime of the font. Additionally, it helps to move the char
// signedness cast to an acceptable place.
uchar buffer[MAXPATHLEN];
QByteArray filename;
if (!CFURLGetFileSystemRepresentation(url, true, buffer, sizeof(buffer))) {
qWarning("QCoreTextFontDatabase::filenameForCFUrl: could not resolve file for URL %s",
qPrintable(QString::fromCFString(CFURLGetString(url))));
} else {
QCFType<CFStringRef> scheme = CFURLCopyScheme(url);
if (QString::fromCFString(scheme) == QLatin1String("qrc"))
filename = ":";
filename += reinterpret_cast<char *>(buffer);
}
return filename;
}
#endif
extern CGAffineTransform qt_transform_from_fontdef(const QFontDef &fontDef);
QFontEngine *QCoreTextFontDatabase::fontEngine(const QFontDef &f, void *usrPtr)
{
CTFontDescriptorRef descriptor = static_cast<CTFontDescriptorRef>(usrPtr);
#ifndef QT_NO_FREETYPE
if (m_useFreeType) {
QCFType<CFURLRef> url(static_cast<CFURLRef>(CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute)));
QByteArray filename;
if (url)
filename = filenameForCFUrl(url);
return freeTypeFontEngine(f, filename);
}
#endif
qreal scaledPointSize = f.pixelSize;
// When 96 DPI is forced, the Mac plugin will use DPI 72 for some
@ -363,7 +410,6 @@ QFontEngine *QCoreTextFontDatabase::fontEngine(const QFontDef &f, void *usrPtr)
if (QGuiApplication::testAttribute(Qt::AA_Use96Dpi))
scaledPointSize = f.pointSize;
CTFontDescriptorRef descriptor = (CTFontDescriptorRef) usrPtr;
CGAffineTransform matrix = qt_transform_from_fontdef(f);
CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, scaledPointSize, &matrix);
if (font) {
@ -385,6 +431,29 @@ static void releaseFontData(void* info, const void* data, size_t size)
QFontEngine *QCoreTextFontDatabase::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
{
#ifndef QT_NO_FREETYPE
if (m_useFreeType) {
QByteArray *fontDataCopy = new QByteArray(fontData);
QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithData(fontDataCopy,
fontDataCopy->constData(), fontDataCopy->size(), releaseFontData);
QCFType<CGFontRef> cgFont(CGFontCreateWithDataProvider(dataProvider));
if (!cgFont) {
qWarning("QCoreTextFontDatabase::fontEngine: CGFontCreateWithDataProvider failed");
return Q_NULLPTR;
}
QFontDef fontDef;
fontDef.pixelSize = pixelSize;
fontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi();
fontDef.hintingPreference = hintingPreference;
CGAffineTransform transform = qt_transform_from_fontdef(fontDef);
QCFType<CTFontRef> ctFont(CTFontCreateWithGraphicsFont(cgFont, fontDef.pixelSize, &transform, Q_NULLPTR));
QCFType<CFURLRef> url(static_cast<CFURLRef>(CTFontCopyAttribute(ctFont, kCTFontURLAttribute)));
return freeTypeFontEngine(fontDef, filenameForCFUrl(url), fontData);
}
#endif
Q_UNUSED(hintingPreference);
QByteArray* fontDataCopy = new QByteArray(fontData);
@ -556,10 +625,36 @@ QStringList QCoreTextFontDatabase::fallbacksForFamily(const QString &family, QFo
}
#if HAVE_CORETEXT
static CFArrayRef createDescriptorArrayForFont(CTFontRef font)
static CFArrayRef createDescriptorArrayForFont(CTFontRef font, const QString &fileName = QString())
{
CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(array, QCFType<CTFontDescriptorRef>(CTFontCopyFontDescriptor(font)));
QCFType<CTFontDescriptorRef> descriptor = CTFontCopyFontDescriptor(font);
Q_UNUSED(fileName)
#ifndef QT_NO_FREETYPE
// 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 if (!fileName.isEmpty()) {
// 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);
}
#endif
CFArrayAppendValue(array, descriptor);
return array;
}
#endif
@ -580,7 +675,11 @@ QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData
if (cgFont) {
if (CTFontManagerRegisterGraphicsFont(cgFont, &error)) {
QCFType<CTFontRef> font = CTFontCreateWithGraphicsFont(cgFont, 0.0, NULL, NULL);
fonts = createDescriptorArrayForFont(font);
fonts = createDescriptorArrayForFont(font
#ifndef QT_NO_FREETYPE
, m_useFreeType ? fileName : QString()
#endif
);
m_applicationFonts.append(QVariant::fromValue(QCFType<CGFontRef>::constructFromGet(cgFont)));
}
}
@ -880,5 +979,25 @@ void QCoreTextFontDatabase::removeApplicationFonts()
#endif
}
#ifndef QT_NO_FREETYPE
QFontEngine *QCoreTextFontDatabase::freeTypeFontEngine(const QFontDef &fontDef, const QByteArray &filename,
const QByteArray &fontData)
{
QFontEngine::FaceId faceId;
faceId.filename = filename;
const bool antialias = !(fontDef.styleStrategy & QFont::NoAntialias);
const QFontEngineFT::GlyphFormat format = antialias ? QFontEngineFT::Format_A8
: QFontEngineFT::Format_Mono;
QScopedPointer<QFontEngineFT> engine(new QFontEngineFT(fontDef));
if (!engine->init(faceId, antialias, format, fontData) || engine->invalid()) {
qWarning() << "QCoreTextFontDatabase::freeTypefontEngine Failed to create engine";
return Q_NULLPTR;
}
return engine.take();
}
#endif
QT_END_NAMESPACE

View File

@ -73,7 +73,7 @@ QT_BEGIN_NAMESPACE
class QCoreTextFontDatabase : public QPlatformFontDatabase
{
public:
QCoreTextFontDatabase();
QCoreTextFontDatabase(bool useFreeType = false);
~QCoreTextFontDatabase();
void populateFontDatabase() Q_DECL_OVERRIDE;
void populateFamily(const QString &familyName) Q_DECL_OVERRIDE;
@ -95,6 +95,11 @@ public:
private:
void populateFromDescriptor(CTFontDescriptorRef font);
#ifndef QT_NO_FREETYPE
bool m_useFreeType;
QFontEngine *freeTypeFontEngine(const QFontDef &fontDef, const QByteArray &filename,
const QByteArray &fontData = QByteArray());
#endif
mutable QString defaultFontName;
void removeApplicationFonts();

View File

@ -50,11 +50,9 @@ public:
QPlatformIntegration * QCocoaIntegrationPlugin::create(const QString& system, const QStringList& paramList)
{
Q_UNUSED(paramList);
QMacAutoReleasePool pool;
if (system.compare(QLatin1String("cocoa"), Qt::CaseInsensitive) == 0)
return new QCocoaIntegration;
return new QCocoaIntegration(paramList);
return 0;
}

View File

@ -97,10 +97,16 @@ public:
class QCocoaIntegration : public QPlatformIntegration
{
public:
QCocoaIntegration();
enum Option {
UseFreeTypeFontEngine = 0x1
};
Q_DECLARE_FLAGS(Options, Option)
QCocoaIntegration(const QStringList &paramList);
~QCocoaIntegration();
static QCocoaIntegration *instance();
Options options() const;
bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE;
QPlatformWindow *createPlatformWindow(QWindow *window) const Q_DECL_OVERRIDE;
@ -141,6 +147,7 @@ public:
void setApplicationIcon(const QIcon &icon) const Q_DECL_OVERRIDE;
private:
static QCocoaIntegration *mInstance;
Options mOptions;
QScopedPointer<QCoreTextFontDatabase> mFontDb;
@ -160,6 +167,8 @@ private:
QList<QCocoaWindow *> m_popupWindowStack;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QCocoaIntegration::Options)
QT_END_NAMESPACE
#endif

View File

@ -244,10 +244,25 @@ QPixmap QCocoaScreen::grabWindow(WId window, int x, int y, int width, int height
return windowPixmap;
}
static QCocoaIntegration::Options parseOptions(const QStringList &paramList)
{
QCocoaIntegration::Options options;
foreach (const QString &param, paramList) {
#ifndef QT_NO_FREETYPE
if (param == QLatin1String("fontengine=freetype"))
options |= QCocoaIntegration::UseFreeTypeFontEngine;
else
#endif
qWarning() << "Unknown option" << param;
}
return options;
}
QCocoaIntegration *QCocoaIntegration::mInstance = 0;
QCocoaIntegration::QCocoaIntegration()
: mFontDb(new QCoreTextFontDatabase())
QCocoaIntegration::QCocoaIntegration(const QStringList &paramList)
: mOptions(parseOptions(paramList))
, mFontDb(new QCoreTextFontDatabase(mOptions.testFlag(UseFreeTypeFontEngine)))
#ifndef QT_NO_ACCESSIBILITY
, mAccessibility(new QCocoaAccessibility)
#endif
@ -345,6 +360,11 @@ QCocoaIntegration *QCocoaIntegration::instance()
return mInstance;
}
QCocoaIntegration::Options QCocoaIntegration::options() const
{
return mOptions;
}
/*!
\brief Synchronizes the screen list, adds new screens, removes deleted ones
*/