Add color font support on Windows

Detect if DirectWrite 2 is available, and support color fonts if possible.
One limitation worth mentioning is that if the color font contains regular,
monochrome glyphs as well, then these will be drawn in black, and not in the
pen color. Fixing this would require some elaborate rewrites in the font
rendering system, since we would have to have two font caches per
color font (one for mono and one for colors), or do some sort of trick where
we make argb(r, g, b, 0) mean subpixel alpha instead, and detect glyphs that
are not correctly premultiplied when blitting to the screen.

Another limitation is that the approach does not work with distance field
rendering. In principle we could support this on Windows, since the
format is vector based, but it would also require substantial work and
it is not possible to support for Apple/Google fonts anyway, so it would
just lead to code which is not cross-platform.

[ChangeLog][Windows] Added support for color fonts (color emojis) when
DirectWrite 2 is available.

Change-Id: I6a608dd5d2aa3a7e762a06830902bddac7c550a5
Reviewed-by: Simon Hausmann <simon.hausmann@theqtcompany.com>
This commit is contained in:
Eskil Abrahamsen Blomfeldt 2015-12-16 15:52:28 +01:00
parent 0b3e45fa0a
commit 33044b83c2
8 changed files with 311 additions and 61 deletions

View File

@ -0,0 +1,50 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the config.tests of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <dwrite_2.h>
#include <d2d1.h>
int main(int, char**)
{
IUnknown *factory = 0;
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory2),
&factory);
return 0;
}

View File

@ -0,0 +1,4 @@
SOURCES = directwrite2.cpp
LIBS += -ldwrite
CONFIG -= qt
CONFIG += console

14
configure vendored
View File

@ -783,6 +783,7 @@ CFG_PCRE=auto
QPA_PLATFORM_GUARD=yes
CFG_STDCXX=auto
CFG_DIRECTWRITE=no
CFG_DIRECTWRITE2=auto
CFG_WERROR=auto
CFG_HEADERSCLEAN=auto
CFG_QREAL=double
@ -6301,6 +6302,18 @@ if [ "$CFG_LARGEFILE" != "yes" ] && [ "$XPLATFORM_MINGW" = "yes" ]; then
CFG_LARGEFILE="yes"
fi
# Detect DirectWrite 2 support on Windows
if [ "$CFG_DIRECTWRITE" = "no" ]; then
CFG_DIRECTWRITE2=no
fi
if [ "$CFG_DIRECTWRITE2" = "auto" ]; then
if compileTest win/directwrite2 "directwrite2"; then
CFG_DIRECTWRITE2=yes
else
CFG_DIRECTWRITE2=no
fi
fi
#-------------------------------------------------------------------------------
# ask for all that hasn't been auto-detected or specified in the arguments
#-------------------------------------------------------------------------------
@ -6568,6 +6581,7 @@ fi
[ "$CFG_XINPUT2" = "yes" ] && QT_CONFIG="$QT_CONFIG xinput2"
[ "$CFG_SYSTEM_PROXIES" = "yes" ] && QT_CONFIG="$QT_CONFIG system-proxies"
[ "$CFG_DIRECTWRITE" = "yes" ] && QT_CONFIG="$QT_CONFIG directwrite"
[ "$CFG_DIRECTWRITE2" = "yes" ] && QT_CONFIG="$QT_CONFIG directwrite2"
[ '!' -z "$DEFINES" ] && QMakeVar add EXTRA_DEFINES "$DEFINES"
[ '!' -z "$INCLUDES" ] && QMakeVar add EXTRA_INCLUDEPATH "$INCLUDES"

View File

@ -62,7 +62,11 @@
#endif
#if !defined(QT_NO_DIRECTWRITE)
# include <dwrite.h>
# if defined(QT_USE_DIRECTWRITE2)
# include <dwrite_2.h>
# else
# include <dwrite.h>
# endif
# include <d2d1.h>
#endif
@ -86,17 +90,27 @@ static inline DWriteCreateFactoryType resolveDWriteCreateFactory()
return reinterpret_cast<DWriteCreateFactoryType>(result);
}
static IDWriteFactory *createDirectWriteFactory()
static void createDirectWriteFactory(IDWriteFactory **factory)
{
*factory = Q_NULLPTR;
static const DWriteCreateFactoryType dWriteCreateFactory = resolveDWriteCreateFactory();
if (!dWriteCreateFactory)
return Q_NULLPTR;
IUnknown *result = Q_NULLPTR;
if (FAILED(dWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &result))) {
qErrnoWarning("DWriteCreateFactory failed");
return Q_NULLPTR;
return;
IUnknown *result = NULL;
#if defined(QT_USE_DIRECTWRITE2)
dWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory2), &result);
#endif
if (result == NULL) {
if (FAILED(dWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &result))) {
qErrnoWarning("DWriteCreateFactory failed");
return;
}
}
return reinterpret_cast<IDWriteFactory *>(result);
*factory = static_cast<IDWriteFactory *>(result);
}
#endif // !QT_NO_DIRECTWRITE
@ -506,8 +520,10 @@ namespace {
class CustomFontFileLoader
{
public:
CustomFontFileLoader() : m_directWriteFactory(createDirectWriteFactory()), m_directWriteFontFileLoader(0)
CustomFontFileLoader() : m_directWriteFontFileLoader(Q_NULLPTR)
{
createDirectWriteFactory(&m_directWriteFactory);
if (m_directWriteFactory) {
m_directWriteFontFileLoader = new DirectWriteFontFileLoader();
m_directWriteFactory->RegisterFontFileLoader(m_directWriteFontFileLoader);
@ -606,7 +622,7 @@ qreal QWindowsFontDatabase::fontSmoothingGamma()
static inline bool initDirectWrite(QWindowsFontEngineData *d)
{
if (!d->directWriteFactory) {
d->directWriteFactory = createDirectWriteFactory();
createDirectWriteFactory(&d->directWriteFactory);
if (!d->directWriteFactory)
return false;
}
@ -1757,10 +1773,7 @@ QFontEngine *QWindowsFontDatabase::createEngine(const QFontDef &request,
}
#if !defined(QT_NO_DIRECTWRITE)
bool useDirectWrite = (request.hintingPreference == QFont::PreferNoHinting)
|| (request.hintingPreference == QFont::PreferVerticalHinting)
|| (QHighDpiScaling::isActive() && request.hintingPreference == QFont::PreferDefaultHinting);
if (useDirectWrite && initDirectWrite(data.data())) {
if (initDirectWrite(data.data())) {
const QString fam = QString::fromWCharArray(lf.lfFaceName);
const QString nameSubstitute = QWindowsFontEngineDirectWrite::fontNameSubstitute(fam);
if (nameSubstitute != fam) {
@ -1782,18 +1795,38 @@ QFontEngine *QWindowsFontDatabase::createEngine(const QFontDef &request,
qWarning().noquote().nospace() << "DirectWrite: CreateFontFaceFromHDC() failed ("
<< errorString << ") for " << request << ' ' << lf << " dpi=" << dpi;
} else {
QWindowsFontEngineDirectWrite *fedw = new QWindowsFontEngineDirectWrite(directWriteFontFace,
request.pixelSize,
data);
bool isColorFont = false;
#if defined(QT_USE_DIRECTWRITE2)
IDWriteFontFace2 *directWriteFontFace2 = Q_NULLPTR;
if (SUCCEEDED(directWriteFontFace->QueryInterface(__uuidof(IDWriteFontFace2),
reinterpret_cast<void **>(&directWriteFontFace2)))) {
if (directWriteFontFace2->IsColorFont())
isColorFont = true;
}
#endif
wchar_t n[64];
GetTextFace(data->hdc, 64, n);
bool useDirectWrite = (request.hintingPreference == QFont::PreferNoHinting)
|| (request.hintingPreference == QFont::PreferVerticalHinting)
|| (QHighDpiScaling::isActive() && request.hintingPreference == QFont::PreferDefaultHinting)
|| isColorFont;
if (useDirectWrite) {
QWindowsFontEngineDirectWrite *fedw = new QWindowsFontEngineDirectWrite(directWriteFontFace,
request.pixelSize,
data);
QFontDef fontDef = request;
fontDef.family = QString::fromWCharArray(n);
wchar_t n[64];
GetTextFace(data->hdc, 64, n);
fedw->initFontInfo(fontDef, dpi);
fe = fedw;
QFontDef fontDef = request;
fontDef.family = QString::fromWCharArray(n);
if (isColorFont)
fedw->glyphFormat = QFontEngine::Format_ARGB;
fedw->initFontInfo(fontDef, dpi);
fe = fedw;
} else {
directWriteFontFace->Release();
}
}
SelectObject(data->hdc, oldFont);

View File

@ -49,7 +49,12 @@
#include <private/qstringiterator_p.h>
#include <QtCore/private/qsystemlibrary_p.h>
#include <dwrite.h>
#if defined(QT_USE_DIRECTWRITE2)
# include <dwrite_2.h>
#else
# include <dwrite.h>
#endif
#include <d2d1.h>
QT_BEGIN_NAMESPACE
@ -562,61 +567,181 @@ QImage QWindowsFontEngineDirectWrite::imageForGlyph(glyph_t t,
RECT rect;
glyphAnalysis->GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1, &rect);
rect.left -= margin;
rect.top -= margin;
rect.right += margin;
rect.bottom += margin;
QRect boundingRect = QRect(QPoint(rect.left - margin,
rect.top - margin),
QPoint(rect.right + margin,
rect.bottom + margin));
const int width = rect.right - rect.left;
const int height = rect.bottom - rect.top;
const int size = width * height * 3;
if (size > 0) {
BYTE *alphaValues = new BYTE[size];
memset(alphaValues, 0, size);
const int width = boundingRect.width() - 1; // -1 due to Qt's off-by-one definition of a QRect
const int height = boundingRect.height() - 1;
hr = glyphAnalysis->CreateAlphaTexture(DWRITE_TEXTURE_CLEARTYPE_3x1,
&rect,
alphaValues,
size);
QImage image;
#if defined(QT_USE_DIRECTWRITE2)
HRESULT hr = DWRITE_E_NOCOLOR;
IDWriteColorGlyphRunEnumerator *enumerator = 0;
IDWriteFactory2 *factory2 = Q_NULLPTR;
if (glyphFormat == QFontEngine::Format_ARGB
&& SUCCEEDED(m_fontEngineData->directWriteFactory->QueryInterface(__uuidof(IDWriteFactory2),
reinterpret_cast<void **>(&factory2)))) {
hr = factory2->TranslateColorGlyphRun(0.0f,
0.0f,
&glyphRun,
NULL,
DWRITE_MEASURING_MODE_NATURAL,
NULL,
0,
&enumerator);
image = QImage(width, height, QImage::Format_ARGB32_Premultiplied);
image.fill(0);
} else
#endif
{
image = QImage(width, height, QImage::Format_RGB32);
image.fill(0xffffffff);
}
if (SUCCEEDED(hr)) {
QImage img(width, height, QImage::Format_RGB32);
img.fill(0xffffffff);
#if defined(QT_USE_DIRECTWRITE2)
BOOL ok = true;
if (SUCCEEDED(hr)) {
while (SUCCEEDED(hr) && ok) {
const DWRITE_COLOR_GLYPH_RUN *colorGlyphRun = 0;
hr = enumerator->GetCurrentRun(&colorGlyphRun);
if (FAILED(hr)) { // No colored runs, only outline
qErrnoWarning(hr, "%s: IDWriteColorGlyphRunEnumerator::GetCurrentRun failed", __FUNCTION__);
break;
}
for (int y=0; y<height; ++y) {
uint *dest = reinterpret_cast<uint *>(img.scanLine(y));
IDWriteGlyphRunAnalysis *colorGlyphsAnalysis = NULL;
hr = m_fontEngineData->directWriteFactory->CreateGlyphRunAnalysis(
&colorGlyphRun->glyphRun,
1.0f,
&transform,
renderMode,
DWRITE_MEASURING_MODE_NATURAL,
0.0, 0.0,
&colorGlyphsAnalysis
);
if (FAILED(hr)) {
qErrnoWarning(hr, "%s: CreateGlyphRunAnalysis failed for color run", __FUNCTION__);
break;
}
float r = qBound(0.0f, colorGlyphRun->runColor.r, 1.0f);
float g = qBound(0.0f, colorGlyphRun->runColor.g, 1.0f);
float b = qBound(0.0f, colorGlyphRun->runColor.b, 1.0f);
float a = qBound(0.0f, colorGlyphRun->runColor.a, 1.0f);
if (!qFuzzyIsNull(a)) {
renderGlyphRun(&image,
r,
g,
b,
a,
colorGlyphsAnalysis,
boundingRect);
}
colorGlyphsAnalysis->Release();
hr = enumerator->MoveNext(&ok);
if (FAILED(hr)) {
qErrnoWarning(hr, "%s: IDWriteColorGlyphRunEnumerator::MoveNext failed", __FUNCTION__);
break;
}
}
} else
#endif
{
renderGlyphRun(&image,
0.0,
0.0,
0.0,
1.0,
glyphAnalysis,
boundingRect);
}
glyphAnalysis->Release();
return image;
} else {
qErrnoWarning(hr, "%s: CreateGlyphRunAnalysis failed", __FUNCTION__);
return QImage();
}
}
void QWindowsFontEngineDirectWrite::renderGlyphRun(QImage *destination,
float r,
float g,
float b,
float a,
IDWriteGlyphRunAnalysis *glyphAnalysis,
const QRect &boundingRect)
{
const int width = destination->width();
const int height = destination->height();
r *= 255.0;
g *= 255.0;
b *= 255.0;
const int size = width * height * 3;
if (size > 0) {
RECT rect;
rect.left = boundingRect.left();
rect.top = boundingRect.top();
rect.right = boundingRect.right();
rect.bottom = boundingRect.bottom();
QVarLengthArray<BYTE, 1024> alphaValueArray(size);
BYTE *alphaValues = alphaValueArray.data();
memset(alphaValues, 0, size);
HRESULT hr = glyphAnalysis->CreateAlphaTexture(DWRITE_TEXTURE_CLEARTYPE_3x1,
&rect,
alphaValues,
size);
if (SUCCEEDED(hr)) {
if (destination->hasAlphaChannel()) {
for (int y = 0; y < height; ++y) {
uint *dest = reinterpret_cast<uint *>(destination->scanLine(y));
BYTE *src = alphaValues + width * 3 * y;
for (int x=0; x<width; ++x) {
dest[x] = *(src) << 16
for (int x = 0; x < width; ++x) {
float redAlpha = a * *src++ / 255.0;
float greenAlpha = a * *src++ / 255.0;
float blueAlpha = a * *src++ / 255.0;
float averageAlpha = (redAlpha + greenAlpha + blueAlpha) / 3.0;
QRgb currentRgb = dest[x];
dest[x] = qRgba(qRound(qRed(currentRgb) * (1.0 - averageAlpha) + averageAlpha * r),
qRound(qGreen(currentRgb) * (1.0 - averageAlpha) + averageAlpha * g),
qRound(qBlue(currentRgb) * (1.0 - averageAlpha) + averageAlpha * b),
qRound(qAlpha(currentRgb) * (1.0 - averageAlpha) + averageAlpha * 255));
}
}
} else {
for (int y = 0; y < height; ++y) {
uint *dest = reinterpret_cast<uint *>(destination->scanLine(y));
BYTE *src = alphaValues + width * 3 * y;
for (int x = 0; x < width; ++x) {
dest[x] = *(src + 0) << 16
| *(src + 1) << 8
| *(src + 2);
src += 3;
}
}
delete[] alphaValues;
glyphAnalysis->Release();
return img;
} else {
delete[] alphaValues;
glyphAnalysis->Release();
qErrnoWarning("%s: CreateAlphaTexture failed", __FUNCTION__);
}
} else {
glyphAnalysis->Release();
qWarning("%s: Glyph has no bounds", __FUNCTION__);
qErrnoWarning("%s: CreateAlphaTexture failed", __FUNCTION__);
}
} else {
qErrnoWarning("%s: CreateGlyphRunAnalysis failed", __FUNCTION__);
glyphAnalysis->Release();
qWarning("%s: Glyph has no bounds", __FUNCTION__);
}
return QImage();
}
QImage QWindowsFontEngineDirectWrite::alphaRGBMapForGlyph(glyph_t t,
@ -734,6 +859,11 @@ glyph_metrics_t QWindowsFontEngineDirectWrite::alphaMapBoundingBox(glyph_t glyph
}
}
QImage QWindowsFontEngineDirectWrite::bitmapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &t)
{
return imageForGlyph(glyph, subPixelPosition, glyphMargin(QFontEngine::Format_A32), t);
}
QT_END_NAMESPACE
#endif // QT_NO_DIRECTWRITE

View File

@ -52,6 +52,7 @@ struct IDWriteFontFace;
struct IDWriteFactory;
struct IDWriteBitmapRenderTarget;
struct IDWriteGdiInterop;
struct IDWriteGlyphRunAnalysis;
QT_BEGIN_NAMESPACE
@ -96,6 +97,7 @@ public:
QImage alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition) Q_DECL_OVERRIDE;
QImage alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &t) Q_DECL_OVERRIDE;
QImage alphaRGBMapForGlyph(glyph_t t, QFixed subPixelPosition, const QTransform &xform) Q_DECL_OVERRIDE;
QImage bitmapForGlyph(glyph_t, QFixed subPixelPosition, const QTransform &t) Q_DECL_OVERRIDE;
QFontEngine *cloneWithSize(qreal pixelSize) const Q_DECL_OVERRIDE;
Qt::HANDLE handle() const Q_DECL_OVERRIDE;
@ -109,6 +111,7 @@ public:
private:
QImage imageForGlyph(glyph_t t, QFixed subPixelPosition, int margin, const QTransform &xform);
void collectMetrics();
void renderGlyphRun(QImage *destination, float r, float g, float b, float a, IDWriteGlyphRunAnalysis *glyphAnalysis, const QRect &boundingRect);
const QSharedPointer<QWindowsFontEngineData> m_fontEngineData;

View File

@ -13,6 +13,9 @@ wince: DEFINES *= QT_LIBINFIX=L"\"\\\"$${QT_LIBINFIX}\\\"\""
DEFINES *= QT_NO_CAST_FROM_ASCII
contains(QT_CONFIG, directwrite) {
contains(QT_CONFIG, directwrite2): \
DEFINES *= QT_USE_DIRECTWRITE2
SOURCES += $$PWD/qwindowsfontenginedirectwrite.cpp
HEADERS += $$PWD/qwindowsfontenginedirectwrite.h
} else {

View File

@ -161,6 +161,7 @@ Configure::Configure(int& argc, char** argv) : verbose(0)
dictionary[ "QML_DEBUG" ] = "yes";
dictionary[ "PLUGIN_MANIFESTS" ] = "no";
dictionary[ "DIRECTWRITE" ] = "auto";
dictionary[ "DIRECTWRITE2" ] = "auto";
dictionary[ "DIRECT2D" ] = "no";
dictionary[ "NIS" ] = "no";
dictionary[ "NEON" ] = "auto";
@ -2235,6 +2236,8 @@ bool Configure::checkAvailability(const QString &part)
available = findFile("mfapi.h") && findFile("mf.lib");
} else if (part == "DIRECTWRITE") {
available = tryCompileProject("win/directwrite");
} else if (part == "DIRECTWRITE2") {
available = tryCompileProject("win/directwrite2");
} else if (part == "DIRECT2D") {
available = tryCompileProject("qpa/direct2d");
} else if (part == "ICONV") {
@ -2488,6 +2491,9 @@ void Configure::autoDetection()
if (dictionary["DIRECTWRITE"] == "auto")
dictionary["DIRECTWRITE"] = checkAvailability("DIRECTWRITE") ? "yes" : "no";
if (dictionary["DIRECTWRITE2"] == "auto")
dictionary["DIRECTWRITE2"] = checkAvailability("DIRECTWRITE2") ? "yes" : "no";
// Mark all unknown "auto" to the default value..
for (QMap<QString,QString>::iterator i = dictionary.begin(); i != dictionary.end(); ++i) {
if (i.value() == "auto")
@ -2903,6 +2909,9 @@ void Configure::generateOutputVars()
if (dictionary["DIRECTWRITE"] == "yes")
qtConfig += "directwrite";
if (dictionary["DIRECTWRITE2"] == "yes")
qtConfig += "directwrite2";
if (dictionary["DIRECT2D"] == "yes")
qtConfig += "direct2d";
@ -3428,6 +3437,9 @@ void Configure::generateQConfigPri()
if (dictionary["DIRECTWRITE"] == "yes")
configStream << " directwrite";
if (dictionary["DIRECTWRITE2"] == "yes")
configStream << " directwrite2";
if (dictionary["ANDROID_STYLE_ASSETS"] == "yes")
configStream << " android-style-assets";
@ -3790,6 +3802,7 @@ void Configure::displayConfig()
sout << "Qt GUI module support......." << dictionary[ "GUI" ] << endl;
sout << "QML debugging..............." << dictionary[ "QML_DEBUG" ] << endl;
sout << "DirectWrite support........." << dictionary[ "DIRECTWRITE" ] << endl;
sout << "DirectWrite 2 support......." << dictionary[ "DIRECTWRITE2" ] << endl;
sout << "Use system proxies.........." << dictionary[ "SYSTEM_PROXIES" ] << endl;
sout << endl;