DirectWrite: Implement support for COLRv1
This traverses the paint node graph and renders the emoji through a generic renderer. This renderer is largely based on the renderer in the FreeType backend and will be used there as well in a follow-up patch. Fixes: QTBUG-113458 Change-Id: I44d17b0be8d85e38589edeb68793bf4ee4491eca Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io> (cherry picked from commit 9f22a51987614ce51b3c12ced420bc4ca5b1ec61) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
e50ae83c8c
commit
0c4b517e6e
@ -234,6 +234,7 @@ qt_internal_add_module(Gui
|
||||
rhi/qshader.cpp rhi/qshader.h rhi/qshader_p.h
|
||||
rhi/qshaderdescription.cpp rhi/qshaderdescription.h rhi/qshaderdescription_p.h
|
||||
text/qabstracttextdocumentlayout.cpp text/qabstracttextdocumentlayout.h text/qabstracttextdocumentlayout_p.h
|
||||
text/qcolrpaintgraphrenderer.cpp text/qcolrpaintgraphrenderer_p.h
|
||||
text/qdistancefield.cpp text/qdistancefield_p.h
|
||||
text/qfont.cpp text/qfont.h text/qfont_p.h
|
||||
text/qfontdatabase.cpp text/qfontdatabase.h text/qfontdatabase_p.h
|
||||
|
@ -623,6 +623,22 @@ int main(int, char **)
|
||||
}
|
||||
")
|
||||
|
||||
# directwritecolrv1
|
||||
qt_config_compile_test(directwritecolrv1
|
||||
LABEL "WINDOWS directwritecolrv1"
|
||||
LIBRARIES
|
||||
dwrite
|
||||
CODE
|
||||
"#include <dwrite_3.h>
|
||||
int main(int, char **)
|
||||
{
|
||||
IUnknown *factory = nullptr;
|
||||
// Just check that the API is available for the build
|
||||
DWRITE_PAINT_ELEMENT paintElement;
|
||||
return 0;
|
||||
}
|
||||
")
|
||||
|
||||
qt_config_compile_test(d2d1
|
||||
LABEL "WINDOWS Direct2D"
|
||||
LIBRARIES
|
||||
@ -691,6 +707,11 @@ qt_feature("directwrite3" PRIVATE
|
||||
CONDITION QT_FEATURE_directwrite AND TEST_directwrite3
|
||||
EMIT_IF WIN32
|
||||
)
|
||||
qt_feature("directwritecolrv1" PRIVATE
|
||||
LABEL "DirectWrite COLRv1 Support"
|
||||
CONDITION QT_FEATURE_directwrite3 AND TEST_directwritecolrv1
|
||||
EMIT_IF WIN32
|
||||
)
|
||||
qt_feature("direct2d" PRIVATE
|
||||
LABEL "Direct 2D"
|
||||
CONDITION WIN32 AND NOT WINRT AND TEST_d2d1
|
||||
@ -1413,6 +1434,7 @@ qt_configure_add_summary_entry(ARGS "direct2d")
|
||||
qt_configure_add_summary_entry(ARGS "direct2d1_1")
|
||||
qt_configure_add_summary_entry(ARGS "directwrite")
|
||||
qt_configure_add_summary_entry(ARGS "directwrite3")
|
||||
qt_configure_add_summary_entry(ARGS "directwritecolrv1")
|
||||
qt_configure_end_summary_section() # end of "Windows" section
|
||||
qt_configure_end_summary_section() # end of "QPA backends" section
|
||||
qt_configure_add_report_entry(
|
||||
|
@ -56,10 +56,6 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
#if defined(QFONTENGINE_FT_SUPPORT_COLRV1)
|
||||
Q_STATIC_LOGGING_CATEGORY(lcColrv1, "qt.text.font.colrv1")
|
||||
#endif
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
#define FLOOR(x) ((x) & -64)
|
||||
|
316
src/gui/text/qcolrpaintgraphrenderer.cpp
Normal file
316
src/gui/text/qcolrpaintgraphrenderer.cpp
Normal file
@ -0,0 +1,316 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include "qcolrpaintgraphrenderer_p.h"
|
||||
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QColrPaintGraphRenderer::~QColrPaintGraphRenderer()
|
||||
{
|
||||
Q_ASSERT(m_oldPaths.isEmpty());
|
||||
Q_ASSERT(m_oldTransforms.isEmpty());
|
||||
|
||||
delete m_painter;
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::save()
|
||||
{
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[enter scope]";
|
||||
|
||||
m_oldPaths.append(m_currentPath);
|
||||
m_oldTransforms.append(m_currentTransform);
|
||||
|
||||
if (m_painter != nullptr)
|
||||
m_painter->save();
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::restore()
|
||||
{
|
||||
m_currentPath = m_oldPaths.takeLast();
|
||||
m_currentTransform = m_oldTransforms.takeLast();
|
||||
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[exit scope]";
|
||||
|
||||
if (m_painter != nullptr)
|
||||
m_painter->restore();
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::setClip(QRect rect)
|
||||
{
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[set clip: " << rect
|
||||
<< "]";
|
||||
if (!isRendering())
|
||||
m_boundingRect = m_boundingRect.united(rect);
|
||||
if (m_painter != nullptr)
|
||||
m_painter->setClipRect(rect);
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::prependTransform(const QTransform &transform)
|
||||
{
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[prepend transform: " << transform
|
||||
<< "]";
|
||||
m_currentTransform = transform * m_currentTransform;
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::appendPath(const QPainterPath &path)
|
||||
{
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[append path: " << path.controlPointRect()
|
||||
<< "]";
|
||||
|
||||
QPainterPath transformedPath(m_currentTransform.map(path));
|
||||
m_currentPath = m_currentPath.united(transformedPath);
|
||||
if (!isRendering())
|
||||
m_boundingRect = m_boundingRect.united(transformedPath.boundingRect());
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::setPath(const QPainterPath &path)
|
||||
{
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[set path]";
|
||||
|
||||
m_currentPath.clear();
|
||||
appendPath(path);
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::setSolidColor(QColor color)
|
||||
{
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[solid " << color
|
||||
<< "]";
|
||||
|
||||
if (m_painter != nullptr)
|
||||
m_painter->setBrush(color);
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::setLinearGradient(QPointF p0, QPointF p1, QPointF p2,
|
||||
QGradient::Spread spread,
|
||||
const QGradientStops &gradientStops)
|
||||
{
|
||||
if (m_painter != nullptr) {
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[linear gradient " << p0 << ", " << p1 << ", " << p2
|
||||
<< ", spread: " << spread
|
||||
<< ", stop count: " << gradientStops.size()
|
||||
<< "]";
|
||||
|
||||
// Calculate new start and end point for single vector gradient preferred by Qt
|
||||
// Find vector perpendicular to p0p2 and project p0p1 onto this to find p3 (final
|
||||
// stop)
|
||||
// https://learn.microsoft.com/en-us/typography/opentype/spec/colr#linear-gradients
|
||||
QVector2D p0p2(p2 - p0);
|
||||
if (qFuzzyIsNull(p0p2.lengthSquared())) {
|
||||
qCWarning(lcColrv1) << "Malformed linear gradient in COLRv1 graph. Points:"
|
||||
<< p0
|
||||
<< p1
|
||||
<< p2;
|
||||
return;
|
||||
}
|
||||
|
||||
// v is perpendicular to p0p2
|
||||
QVector2D v(p0p2.y(), -p0p2.x());
|
||||
|
||||
// u is the vector from p0 to p1
|
||||
QVector2D u(p1 - p0);
|
||||
if (qFuzzyIsNull(u.lengthSquared())) {
|
||||
qCWarning(lcColrv1) << "Malformed linear gradient in COLRv1 graph. Points:"
|
||||
<< p0
|
||||
<< p1
|
||||
<< p2;
|
||||
return;
|
||||
}
|
||||
|
||||
// We find the projected point p3
|
||||
QPointF p3((QVector2D(p0) + v * QVector2D::dotProduct(u, v) / v.lengthSquared()).toPointF());
|
||||
|
||||
p0 = m_currentTransform.map(p0);
|
||||
p3 = m_currentTransform.map(p1);
|
||||
|
||||
QLinearGradient linearGradient(p0, p3);
|
||||
linearGradient.setSpread(spread);
|
||||
linearGradient.setStops(gradientStops);
|
||||
linearGradient.setCoordinateMode(QGradient::LogicalMode);
|
||||
|
||||
m_painter->setBrush(linearGradient);
|
||||
}
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::setConicalGradient(QPointF center,
|
||||
qreal startAngle, qreal endAngle,
|
||||
QGradient::Spread spread,
|
||||
const QGradientStops &gradientStops)
|
||||
{
|
||||
if (m_painter != nullptr) {
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[conical gradient " << center
|
||||
<< ", startAngle=" << startAngle
|
||||
<< ", endAngle=" << endAngle
|
||||
<< ", spread: " << spread
|
||||
<< ", stop count: " << gradientStops.size()
|
||||
<< "]";
|
||||
|
||||
QConicalGradient conicalGradient(center, startAngle);
|
||||
conicalGradient.setSpread(spread);
|
||||
|
||||
// Adapt stops to actual span since Qt always assumes end angle of 360
|
||||
// Note: This does not give accurate results for the colors outside the angle span.
|
||||
// To do this correctly, we would have to insert stops at 0°, 360° and if the spread
|
||||
// is reflect/repeat, also throughout the uncovered area to get the correct
|
||||
// rendering. It might however be easier to support this in QConicalGradient itself.
|
||||
// For now, this is left only semi-supported, as sweep gradients are currently rare.
|
||||
const qreal multiplier = qFuzzyCompare(endAngle, startAngle)
|
||||
? 1.0
|
||||
: (endAngle - startAngle) / 360.0;
|
||||
|
||||
QGradientStops adaptedStops;
|
||||
adaptedStops.reserve(gradientStops.size());
|
||||
|
||||
for (const QGradientStop &gradientStop : gradientStops)
|
||||
adaptedStops.append(qMakePair(gradientStop.first * multiplier, gradientStop.second));
|
||||
|
||||
conicalGradient.setStops(adaptedStops);
|
||||
conicalGradient.setCoordinateMode(QGradient::LogicalMode);
|
||||
|
||||
m_painter->setBrush(conicalGradient);
|
||||
}
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::setRadialGradient(QPointF c0, qreal r0,
|
||||
QPointF c1, qreal r1,
|
||||
QGradient::Spread spread,
|
||||
const QGradientStops &gradientStops)
|
||||
{
|
||||
if (m_painter != nullptr) {
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[radial gradient " << c0
|
||||
<< ", rad=" << r0
|
||||
<< ", " << c1
|
||||
<< ", rad=" << r1
|
||||
<< ", spread: " << spread
|
||||
<< ", stop count: " << gradientStops.size()
|
||||
<< "]";
|
||||
|
||||
QPointF c0e(c0 + QPointF(r0, 0.0));
|
||||
QPointF c1e(c1 + QPointF(r1, 0.0));
|
||||
|
||||
c0 = m_currentTransform.map(c0);
|
||||
c0e = m_currentTransform.map(c0e);
|
||||
c1 = m_currentTransform.map(c1);
|
||||
c1e = m_currentTransform.map(c1e);
|
||||
|
||||
const QVector2D d0(c0e - c0);
|
||||
const QVector2D d1(c1e - c1);
|
||||
|
||||
QRadialGradient gradient(c1, d1.length(), c0, d0.length());
|
||||
gradient.setCoordinateMode(QGradient::LogicalMode);
|
||||
gradient.setSpread(spread);
|
||||
gradient.setStops(gradientStops);
|
||||
m_painter->setBrush(gradient);
|
||||
}
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::drawCurrentPath()
|
||||
{
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[draw path " << m_currentPath.controlPointRect() << m_currentTransform
|
||||
<< "]";
|
||||
|
||||
if (m_painter != nullptr)
|
||||
m_painter->drawPath(m_currentPath);
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::beginCalculateBoundingBox()
|
||||
{
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[begin calculate bounding box]";
|
||||
|
||||
Q_ASSERT(m_painter == nullptr);
|
||||
m_boundingRect = QRect{};
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::drawImage(const QImage &image)
|
||||
{
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[draw image: " << image.size() << "]";
|
||||
|
||||
if (m_painter != nullptr) {
|
||||
m_painter->setWorldMatrixEnabled(false);
|
||||
m_painter->drawImage(QPoint(0, 0), image);
|
||||
m_painter->setWorldMatrixEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::setCompositionMode(QPainter::CompositionMode mode)
|
||||
{
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[set composition mode: " << mode << "]";
|
||||
|
||||
if (m_painter != nullptr)
|
||||
m_painter->setCompositionMode(mode);
|
||||
}
|
||||
|
||||
void QColrPaintGraphRenderer::beginRender(qreal pixelSizeScale, const QTransform &transform)
|
||||
{
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[begin render scale: " << pixelSizeScale
|
||||
<< ", transform: " << transform
|
||||
<< "]";
|
||||
|
||||
if (m_boundingRect.isEmpty())
|
||||
return;
|
||||
|
||||
QRect alignedRect(m_boundingRect.toAlignedRect());
|
||||
|
||||
m_image = QImage(alignedRect.size(), QImage::Format_ARGB32_Premultiplied);
|
||||
m_image.fill(Qt::transparent);
|
||||
|
||||
Q_ASSERT(m_painter == nullptr);
|
||||
m_painter = new QPainter;
|
||||
m_painter->begin(&m_image);
|
||||
m_painter->setRenderHint(QPainter::Antialiasing);
|
||||
m_painter->setPen(Qt::NoPen);
|
||||
m_painter->setBrush(Qt::NoBrush);
|
||||
|
||||
m_painter->translate(-alignedRect.topLeft());
|
||||
|
||||
// Scale from normalized coordinates
|
||||
m_painter->scale(pixelSizeScale, pixelSizeScale);
|
||||
m_painter->setWorldTransform(transform, true);
|
||||
}
|
||||
|
||||
QImage QColrPaintGraphRenderer::endRender()
|
||||
{
|
||||
qCDebug(lcColrv1).noquote().nospace()
|
||||
<< QByteArray().fill(' ', m_oldPaths.size() * 2)
|
||||
<< "[end image size: " << m_image.size()
|
||||
<< "]";
|
||||
|
||||
Q_ASSERT(m_painter != nullptr);
|
||||
m_painter->end();
|
||||
delete m_painter;
|
||||
m_painter = nullptr;
|
||||
|
||||
return m_image;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
81
src/gui/text/qcolrpaintgraphrenderer_p.h
Normal file
81
src/gui/text/qcolrpaintgraphrenderer_p.h
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#ifndef QCOLRPAINTGRAPHRENDERER_P_H
|
||||
#define QCOLRPAINTGRAPHRENDERER_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include <QtGui/qpainter.h>
|
||||
#include <QtGui/qpainterpath.h>
|
||||
#include <private/qfontengine_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QColrPaintGraphRenderer
|
||||
{
|
||||
public:
|
||||
~QColrPaintGraphRenderer();
|
||||
|
||||
void setBoundingRect(QRectF boundingRect) { m_boundingRect = boundingRect; }
|
||||
QRectF boundingRect() const { return m_boundingRect; }
|
||||
|
||||
QTransform currentTransform() const { return m_currentTransform; }
|
||||
QPainterPath currentPath() const { return m_currentPath; }
|
||||
|
||||
void save();
|
||||
void restore();
|
||||
|
||||
void appendPath(const QPainterPath &path);
|
||||
void setPath(const QPainterPath &path);
|
||||
|
||||
void prependTransform(const QTransform &transform);
|
||||
|
||||
void setSolidColor(QColor color);
|
||||
void setRadialGradient(QPointF c0, qreal r0,
|
||||
QPointF c1, qreal r1,
|
||||
QGradient::Spread spread,
|
||||
const QGradientStops &gradientStops);
|
||||
void setLinearGradient(QPointF p0, QPointF p1, QPointF p2,
|
||||
QGradient::Spread spread,
|
||||
const QGradientStops &gradientStops);
|
||||
void setConicalGradient(QPointF center,
|
||||
qreal startAngle,
|
||||
qreal endAngle,
|
||||
QGradient::Spread spread,
|
||||
const QGradientStops &gradientStops);
|
||||
void setCompositionMode(QPainter::CompositionMode mode);
|
||||
|
||||
void drawCurrentPath();
|
||||
void drawImage(const QImage &image);
|
||||
|
||||
void setClip(QRect rect);
|
||||
|
||||
void beginCalculateBoundingBox();
|
||||
void beginRender(qreal pixelSizeScale, const QTransform &transform);
|
||||
QImage endRender();
|
||||
bool isRendering() const { return m_painter != nullptr; }
|
||||
|
||||
private:
|
||||
QImage m_image;
|
||||
QPainter *m_painter = nullptr;
|
||||
QTransform m_currentTransform;
|
||||
QRectF m_boundingRect;
|
||||
QPainterPath m_currentPath;
|
||||
|
||||
QList<QPainterPath> m_oldPaths;
|
||||
QList<QTransform> m_oldTransforms;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QCOLRPAINTGRAPHRENDERER_P_H
|
@ -28,6 +28,8 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_LOGGING_CATEGORY(lcColrv1, "qt.text.font.colrv1")
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static inline bool qtransform_equals_no_translate(const QTransform &a, const QTransform &b)
|
||||
|
@ -42,6 +42,8 @@ enum HB_Compat_Error {
|
||||
typedef void (*qt_destroy_func_t) (void *user_data);
|
||||
typedef bool (*qt_get_font_table_func_t) (void *user_data, uint tag, uchar *buffer, uint *length);
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcColrv1)
|
||||
|
||||
class Q_GUI_EXPORT QFontEngine
|
||||
{
|
||||
public:
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include <qpa/qplatformintegration.h>
|
||||
#include <QtGui/qpainterpath.h>
|
||||
|
||||
#include <private/qcolrpaintgraphrenderer_p.h>
|
||||
|
||||
#if QT_CONFIG(directwrite3)
|
||||
# include "qwindowsdirectwritefontdatabase_p.h"
|
||||
# include <dwrite_3.h>
|
||||
@ -582,16 +584,16 @@ void QWindowsFontEngineDirectWrite::recalcAdvances(QGlyphLayout *glyphs, QFontEn
|
||||
}
|
||||
}
|
||||
|
||||
void QWindowsFontEngineDirectWrite::getUnscaledGlyph(glyph_t glyph,
|
||||
QPainterPath *path,
|
||||
glyph_metrics_t *metric)
|
||||
QPainterPath QWindowsFontEngineDirectWrite::unscaledGlyph(glyph_t glyph) const
|
||||
{
|
||||
float advance = 0.0f;
|
||||
UINT16 g = glyph;
|
||||
DWRITE_GLYPH_OFFSET offset;
|
||||
offset.advanceOffset = 0;
|
||||
offset.ascenderOffset = 0;
|
||||
GeometrySink geometrySink(path);
|
||||
|
||||
QPainterPath ret;
|
||||
GeometrySink geometrySink(&ret);
|
||||
HRESULT hr = m_directWriteFontFace->GetGlyphRunOutline(m_unitsPerEm,
|
||||
&g,
|
||||
&advance,
|
||||
@ -600,13 +602,21 @@ void QWindowsFontEngineDirectWrite::getUnscaledGlyph(glyph_t glyph,
|
||||
false,
|
||||
false,
|
||||
&geometrySink);
|
||||
if (FAILED(hr)) {
|
||||
if (FAILED(hr))
|
||||
qErrnoWarning("%s: GetGlyphRunOutline failed", __FUNCTION__);
|
||||
return;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void QWindowsFontEngineDirectWrite::getUnscaledGlyph(glyph_t glyph,
|
||||
QPainterPath *path,
|
||||
glyph_metrics_t *metric)
|
||||
{
|
||||
*path = unscaledGlyph(glyph);
|
||||
|
||||
UINT16 g = glyph;
|
||||
DWRITE_GLYPH_METRICS glyphMetrics;
|
||||
hr = m_directWriteFontFace->GetDesignGlyphMetrics(&g, 1, &glyphMetrics);
|
||||
HRESULT hr = m_directWriteFontFace->GetDesignGlyphMetrics(&g, 1, &glyphMetrics);
|
||||
if (FAILED(hr)) {
|
||||
qErrnoWarning("%s: GetDesignGlyphMetrics failed", __FUNCTION__);
|
||||
return;
|
||||
@ -838,6 +848,515 @@ bool QWindowsFontEngineDirectWrite::renderColr0GlyphRun(QImage *image,
|
||||
return true;
|
||||
}
|
||||
|
||||
#if QT_CONFIG(directwritecolrv1)
|
||||
static inline QTransform matrixToTransform(const DWRITE_MATRIX &matrix,
|
||||
int unitsPerEm = 1)
|
||||
{
|
||||
return QTransform(matrix.m11, matrix.m12,
|
||||
matrix.m21, matrix.m22,
|
||||
matrix.dx * unitsPerEm, matrix.dy * unitsPerEm);
|
||||
}
|
||||
|
||||
static inline QColor colorToColor(const DWRITE_COLOR_F &color, float alphaMultiplier = 1.0)
|
||||
{
|
||||
return QColor::fromRgbF(color.r, color.g, color.b, color.a * alphaMultiplier);
|
||||
}
|
||||
|
||||
static inline QGradient::Spread extendToSpread(UINT32 extendMode)
|
||||
{
|
||||
switch (extendMode) {
|
||||
case D2D1_EXTEND_MODE_WRAP: return QGradient::RepeatSpread;
|
||||
case D2D1_EXTEND_MODE_MIRROR: return QGradient::ReflectSpread;
|
||||
default: return QGradient::PadSpread;
|
||||
};
|
||||
}
|
||||
|
||||
static inline QPainter::CompositionMode compositeToCompositionMode(DWRITE_COLOR_COMPOSITE_MODE mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case DWRITE_COLOR_COMPOSITE_CLEAR:
|
||||
return QPainter::CompositionMode_Clear;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_SRC:
|
||||
return QPainter::CompositionMode_Source;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_DEST:
|
||||
return QPainter::CompositionMode_Destination;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_SRC_OVER:
|
||||
return QPainter::CompositionMode_SourceOver;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_DEST_OVER:
|
||||
return QPainter::CompositionMode_DestinationOver;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_SRC_IN:
|
||||
return QPainter::CompositionMode_SourceIn;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_DEST_IN:
|
||||
return QPainter::CompositionMode_DestinationIn;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_SRC_OUT:
|
||||
return QPainter::CompositionMode_SourceOut;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_DEST_OUT:
|
||||
return QPainter::CompositionMode_DestinationOut;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_SRC_ATOP:
|
||||
return QPainter::CompositionMode_SourceAtop;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_DEST_ATOP:
|
||||
return QPainter::CompositionMode_DestinationAtop;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_XOR:
|
||||
return QPainter::CompositionMode_Xor;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_PLUS:
|
||||
return QPainter::CompositionMode_Plus;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_SCREEN:
|
||||
return QPainter::CompositionMode_Screen;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_OVERLAY:
|
||||
return QPainter::CompositionMode_Overlay;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_DARKEN:
|
||||
return QPainter::CompositionMode_Darken;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_LIGHTEN:
|
||||
return QPainter::CompositionMode_Lighten;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_COLOR_DODGE:
|
||||
return QPainter::CompositionMode_ColorDodge;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_COLOR_BURN:
|
||||
return QPainter::CompositionMode_ColorBurn;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_HARD_LIGHT:
|
||||
return QPainter::CompositionMode_HardLight;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_SOFT_LIGHT:
|
||||
return QPainter::CompositionMode_SoftLight;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_DIFFERENCE:
|
||||
return QPainter::CompositionMode_Difference;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_EXCLUSION:
|
||||
return QPainter::CompositionMode_Exclusion;
|
||||
|
||||
case DWRITE_COLOR_COMPOSITE_MULTIPLY:
|
||||
return QPainter::CompositionMode_Multiply;
|
||||
|
||||
default:
|
||||
qCWarning(lcColrv1) << "Unhandled color composite mode:" << mode;
|
||||
return QPainter::CompositionMode_SourceOver;
|
||||
};
|
||||
}
|
||||
#endif // QT_CONFIG(directwritecolrv1)
|
||||
|
||||
QRect QWindowsFontEngineDirectWrite::paintGraphBounds(glyph_t glyph,
|
||||
const DWRITE_MATRIX &matrix) const
|
||||
{
|
||||
#if QT_CONFIG(directwritecolrv1)
|
||||
ComPtr<IDWriteFontFace7> face7;
|
||||
HRESULT hr = m_directWriteFontFace->QueryInterface(__uuidof(IDWriteFontFace7),
|
||||
&face7);
|
||||
if (SUCCEEDED(hr)) {
|
||||
DWRITE_PAINT_FEATURE_LEVEL featureLevel = face7->GetPaintFeatureLevel(DWRITE_GLYPH_IMAGE_FORMATS_COLR_PAINT_TREE);
|
||||
if (featureLevel != DWRITE_PAINT_FEATURE_LEVEL_COLR_V1)
|
||||
return QRect{};
|
||||
|
||||
ComPtr<IDWritePaintReader> paintReader;
|
||||
hr = face7->CreatePaintReader(DWRITE_GLYPH_IMAGE_FORMATS_COLR_PAINT_TREE,
|
||||
featureLevel,
|
||||
&paintReader);
|
||||
if (FAILED(hr)) {
|
||||
qErrnoWarning(hr, "%s: CreatePaintReader failed", __FUNCTION__);
|
||||
return QRect{};
|
||||
}
|
||||
|
||||
DWRITE_PAINT_ELEMENT paintElement;
|
||||
D2D_RECT_F clipBox;
|
||||
hr = paintReader->SetCurrentGlyph(glyph,
|
||||
&paintElement,
|
||||
sizeof(paintElement),
|
||||
&clipBox,
|
||||
nullptr);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
qErrnoWarning(hr, "%s: SetCurrentGlyph failed", __FUNCTION__);
|
||||
return QRect{};
|
||||
}
|
||||
|
||||
if (paintElement.paintType == DWRITE_PAINT_TYPE_NONE)
|
||||
return QRect{};
|
||||
|
||||
QRectF boundingRect = QRectF(QPointF(clipBox.left, clipBox.top),
|
||||
QPointF(clipBox.right, clipBox.bottom));
|
||||
if (boundingRect.isNull()) {
|
||||
QColrPaintGraphRenderer boundingRectCalculator;
|
||||
boundingRectCalculator.beginCalculateBoundingBox();
|
||||
if (traverseColr1(paintReader.Get(), face7.Get(), &paintElement, &boundingRectCalculator))
|
||||
boundingRect = boundingRectCalculator.boundingRect();
|
||||
}
|
||||
|
||||
QTransform initialTransform;
|
||||
initialTransform.scale(fontDef.pixelSize, fontDef.pixelSize);
|
||||
boundingRect = initialTransform.mapRect(boundingRect);
|
||||
|
||||
QTransform originalXform = matrixToTransform(matrix);
|
||||
boundingRect = originalXform.mapRect(boundingRect);
|
||||
|
||||
return boundingRect.toAlignedRect();
|
||||
} else {
|
||||
qCDebug(lcColrv1) << "Font face does not support IDWriteFontFace7 interface";
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(glyph);
|
||||
Q_UNUSED(matrix);
|
||||
#endif
|
||||
|
||||
return QRect{};
|
||||
}
|
||||
|
||||
#if QT_CONFIG(directwritecolrv1)
|
||||
bool QWindowsFontEngineDirectWrite::traverseColr1(IDWritePaintReader *paintReader,
|
||||
IDWriteFontFace7 *face7,
|
||||
const DWRITE_PAINT_ELEMENT *paintElement,
|
||||
QColrPaintGraphRenderer *paintGraphRenderer) const
|
||||
{
|
||||
paintGraphRenderer->save();
|
||||
auto cleanup = qScopeGuard([&paintGraphRenderer]() {
|
||||
paintGraphRenderer->restore();
|
||||
});
|
||||
|
||||
auto traverseChildren = [&](quint32 childCount) {
|
||||
DWRITE_PAINT_ELEMENT childPaintElement;
|
||||
if (FAILED(paintReader->MoveToFirstChild(&childPaintElement)))
|
||||
return false;
|
||||
|
||||
while (childCount-- > 0) {
|
||||
traverseColr1(paintReader, face7, &childPaintElement, paintGraphRenderer);
|
||||
if (childCount > 0) {
|
||||
if (FAILED(paintReader->MoveToNextSibling(&childPaintElement))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SUCCEEDED(paintReader->MoveToParent());
|
||||
};
|
||||
|
||||
auto collectStops = [&paintReader](int stopCount) {
|
||||
QGradientStops ret;
|
||||
|
||||
QVarLengthArray<D2D1_GRADIENT_STOP> stops(stopCount);
|
||||
HRESULT hr = paintReader->GetGradientStops(0, stopCount, stops.data());
|
||||
if (FAILED(hr))
|
||||
return ret;
|
||||
|
||||
for (int i = 0; i < stopCount; ++i) {
|
||||
const D2D1_GRADIENT_STOP &stop = stops[i];
|
||||
QColor color = QColor::fromRgbF(stop.color.r, stop.color.g, stop.color.b, stop.color.a);
|
||||
ret.append(qMakePair(stop.position, color));
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
switch (paintElement->paintType) {
|
||||
case DWRITE_PAINT_TYPE_LAYERS:
|
||||
{
|
||||
if (!traverseChildren(paintElement->paint.layers.childCount))
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DWRITE_PAINT_TYPE_SOLID_GLYPH:
|
||||
{
|
||||
QPainterPath glyphPath = unscaledGlyph(paintElement->paint.solidGlyph.glyphIndex);
|
||||
|
||||
QColor color = colorToColor(paintElement->paint.solidGlyph.color.value);
|
||||
paintGraphRenderer->setPath(glyphPath);
|
||||
paintGraphRenderer->setSolidColor(color);
|
||||
paintGraphRenderer->drawCurrentPath();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DWRITE_PAINT_TYPE_SOLID:
|
||||
{
|
||||
QColor color = colorToColor(paintElement->paint.solid.value,
|
||||
paintElement->paint.solid.alphaMultiplier);
|
||||
paintGraphRenderer->setSolidColor(color);
|
||||
paintGraphRenderer->drawCurrentPath();
|
||||
break;
|
||||
}
|
||||
|
||||
case DWRITE_PAINT_TYPE_COMPOSITE:
|
||||
{
|
||||
if (!paintGraphRenderer->isRendering()) {
|
||||
traverseChildren(2);
|
||||
} else {
|
||||
DWRITE_PAINT_ELEMENT childElement;
|
||||
|
||||
HRESULT hr = paintReader->MoveToFirstChild(&childElement);
|
||||
if (FAILED(hr)) {
|
||||
qErrnoWarning(hr, "%s: Cannot move to first child of composite node",
|
||||
__FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
// First draw back drop which is the second child
|
||||
hr = paintReader->MoveToNextSibling(&childElement);
|
||||
if (FAILED(hr)) {
|
||||
qErrnoWarning(hr, "%s: Cannot move to second child of composite node",
|
||||
__FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWRITE_COLOR_COMPOSITE_MODE compositeMode = paintElement->paint.composite.mode;
|
||||
|
||||
QColrPaintGraphRenderer compositeRenderer;
|
||||
compositeRenderer.setBoundingRect(paintGraphRenderer->boundingRect());
|
||||
compositeRenderer.beginRender(fontDef.pixelSize / m_unitsPerEm,
|
||||
paintGraphRenderer->currentTransform());
|
||||
if (!traverseColr1(paintReader, face7, &childElement, &compositeRenderer))
|
||||
return false;
|
||||
|
||||
// Then draw source which is the first child
|
||||
hr = paintReader->MoveToParent();
|
||||
if (FAILED(hr)) {
|
||||
qErrnoWarning(hr, "%s: Cannot move back to parent composite node",
|
||||
__FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = paintReader->MoveToFirstChild(&childElement);
|
||||
if (FAILED(hr)) {
|
||||
qErrnoWarning(hr, "%s: Cannot move to first child of composite node",
|
||||
__FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
compositeRenderer.setCompositionMode(compositeToCompositionMode(compositeMode));
|
||||
if (!traverseColr1(paintReader, face7, &childElement, &compositeRenderer))
|
||||
return false;
|
||||
|
||||
hr = paintReader->MoveToParent();
|
||||
if (FAILED(hr)) {
|
||||
qErrnoWarning(hr, "%s: Cannot move back to parent composite node",
|
||||
__FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
paintGraphRenderer->drawImage(compositeRenderer.endRender());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DWRITE_PAINT_TYPE_RADIAL_GRADIENT:
|
||||
{
|
||||
const QPointF c0(paintElement->paint.radialGradient.x0 * m_unitsPerEm,
|
||||
paintElement->paint.radialGradient.y0 * m_unitsPerEm);
|
||||
const QPointF c1(paintElement->paint.radialGradient.x1 * m_unitsPerEm,
|
||||
paintElement->paint.radialGradient.y1 * m_unitsPerEm);
|
||||
const qreal r0 = paintElement->paint.radialGradient.radius0 * m_unitsPerEm;
|
||||
const qreal r1 = paintElement->paint.radialGradient.radius1 * m_unitsPerEm;
|
||||
|
||||
const QGradient::Spread spread
|
||||
= extendToSpread(paintElement->paint.radialGradient.extendMode);
|
||||
|
||||
const QGradientStops gradientStops
|
||||
= collectStops(paintElement->paint.radialGradient.gradientStopCount);
|
||||
|
||||
paintGraphRenderer->setRadialGradient(c0, r0, c1, r1, spread, gradientStops);
|
||||
paintGraphRenderer->drawCurrentPath();
|
||||
break;
|
||||
}
|
||||
|
||||
case DWRITE_PAINT_TYPE_SWEEP_GRADIENT:
|
||||
{
|
||||
const QPointF center(paintElement->paint.sweepGradient.centerX * m_unitsPerEm,
|
||||
paintElement->paint.sweepGradient.centerY * m_unitsPerEm);
|
||||
const qreal startAngle = paintElement->paint.sweepGradient.startAngle;
|
||||
const qreal endAngle = paintElement->paint.sweepGradient.endAngle;
|
||||
|
||||
const QGradient::Spread spread
|
||||
= extendToSpread(paintElement->paint.radialGradient.extendMode);
|
||||
const QGradientStops gradientStops
|
||||
= collectStops(paintElement->paint.radialGradient.gradientStopCount);
|
||||
|
||||
paintGraphRenderer->setConicalGradient(center,
|
||||
startAngle,
|
||||
endAngle,
|
||||
spread,
|
||||
gradientStops);
|
||||
paintGraphRenderer->drawCurrentPath();
|
||||
break;
|
||||
}
|
||||
|
||||
case DWRITE_PAINT_TYPE_LINEAR_GRADIENT:
|
||||
{
|
||||
const QPointF p0(paintElement->paint.linearGradient.x0 * m_unitsPerEm,
|
||||
paintElement->paint.linearGradient.y0 * m_unitsPerEm);
|
||||
const QPointF p1(paintElement->paint.linearGradient.x1 * m_unitsPerEm,
|
||||
paintElement->paint.linearGradient.y1 * m_unitsPerEm);
|
||||
const QPointF p2(paintElement->paint.linearGradient.x2 * m_unitsPerEm,
|
||||
paintElement->paint.linearGradient.y2 * m_unitsPerEm);
|
||||
|
||||
const QGradient::Spread spread
|
||||
= extendToSpread(paintElement->paint.linearGradient.extendMode);
|
||||
const QGradientStops gradientStops
|
||||
= collectStops(paintElement->paint.linearGradient.gradientStopCount);
|
||||
|
||||
paintGraphRenderer->setLinearGradient(p0, p1, p2, spread, gradientStops);
|
||||
paintGraphRenderer->drawCurrentPath();
|
||||
break;
|
||||
}
|
||||
|
||||
case DWRITE_PAINT_TYPE_GLYPH:
|
||||
{
|
||||
QPainterPath glyphPath = unscaledGlyph(paintElement->paint.glyph.glyphIndex);
|
||||
paintGraphRenderer->appendPath(glyphPath);
|
||||
if (!traverseChildren(1))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
case DWRITE_PAINT_TYPE_COLOR_GLYPH:
|
||||
{
|
||||
D2D_RECT_F rect = paintElement->paint.colorGlyph.clipBox;
|
||||
QRect clipBox = QRectF(QPointF(rect.left, rect.top),
|
||||
QPointF(rect.right, rect.bottom)).toAlignedRect();
|
||||
if (!clipBox.isEmpty()) {
|
||||
QTransform coordinatesTransform;
|
||||
coordinatesTransform.scale(m_unitsPerEm, m_unitsPerEm);
|
||||
clipBox = coordinatesTransform.mapRect(clipBox);
|
||||
|
||||
paintGraphRenderer->setClip(clipBox);
|
||||
}
|
||||
|
||||
DWRITE_PAINT_ELEMENT childElement;
|
||||
if (FAILED(paintReader->MoveToFirstChild(&childElement)))
|
||||
return false;
|
||||
|
||||
if (!traverseColr1(paintReader, face7, &childElement, paintGraphRenderer))
|
||||
return false;
|
||||
|
||||
if (FAILED(paintReader->MoveToParent()))
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DWRITE_PAINT_TYPE_TRANSFORM:
|
||||
{
|
||||
QTransform transform = matrixToTransform(paintElement->paint.transform, m_unitsPerEm);
|
||||
paintGraphRenderer->prependTransform(transform);
|
||||
if (!traverseChildren(1))
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
qCDebug(lcColrv1) << "Unhandled paint graph node type" << paintElement->paintType;
|
||||
break;
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QWindowsFontEngineDirectWrite::renderColr1GlyphRun(QImage *image,
|
||||
const DWRITE_GLYPH_RUN *glyphRun,
|
||||
const DWRITE_MATRIX &matrix,
|
||||
QColor color) const
|
||||
{
|
||||
qCDebug(lcColrv1) << "renderColr1GlyphRun,"
|
||||
<< "families:" << fontDef.families;
|
||||
ComPtr<IDWriteFontFace7> face7;
|
||||
HRESULT hr = m_directWriteFontFace->QueryInterface(__uuidof(IDWriteFontFace7),
|
||||
&face7);
|
||||
if (SUCCEEDED(hr)) {
|
||||
DWRITE_PAINT_FEATURE_LEVEL featureLevel =
|
||||
face7->GetPaintFeatureLevel(DWRITE_GLYPH_IMAGE_FORMATS_COLR_PAINT_TREE);
|
||||
if (featureLevel != DWRITE_PAINT_FEATURE_LEVEL_COLR_V1) {
|
||||
qCDebug(lcColrv1) << "Unsupported feature level:" << featureLevel;
|
||||
return true;
|
||||
}
|
||||
|
||||
ComPtr<IDWritePaintReader> paintReader;
|
||||
hr = face7->CreatePaintReader(DWRITE_GLYPH_IMAGE_FORMATS_COLR_PAINT_TREE,
|
||||
featureLevel,
|
||||
&paintReader);
|
||||
if (FAILED(hr)) {
|
||||
qErrnoWarning(hr, "%s: CreatePaintReader failed", __FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(glyphRun->glyphCount == 1);
|
||||
DWRITE_PAINT_ELEMENT paintElement;
|
||||
D2D_RECT_F clipBox;
|
||||
hr = paintReader->SetCurrentGlyph(glyphRun->glyphIndices[0],
|
||||
&paintElement,
|
||||
&clipBox,
|
||||
nullptr);
|
||||
if (FAILED(hr)) {
|
||||
qErrnoWarning(hr, "%s: SetCurrentGlyph failed", __FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (paintElement.paintType == DWRITE_PAINT_TYPE_NONE) {
|
||||
qCDebug(lcColrv1) << "Glyph" << glyphRun->glyphIndices[0]
|
||||
<< "does not have a paint graph";
|
||||
return false;
|
||||
}
|
||||
|
||||
DWRITE_COLOR_F dwColor;
|
||||
dwColor.r = color.redF();
|
||||
dwColor.g = color.greenF();
|
||||
dwColor.b = color.blueF();
|
||||
dwColor.a = color.alphaF();
|
||||
paintReader->SetTextColor(dwColor);
|
||||
|
||||
QRectF boundingRect = QRectF(QPointF(clipBox.left, clipBox.top),
|
||||
QPointF(clipBox.right, clipBox.bottom));
|
||||
if (boundingRect.isNull()) {
|
||||
QColrPaintGraphRenderer boundingRectCalculator;
|
||||
boundingRectCalculator.beginCalculateBoundingBox();
|
||||
if (traverseColr1(paintReader.Get(), face7.Get(), &paintElement, &boundingRectCalculator))
|
||||
boundingRect = boundingRectCalculator.boundingRect();
|
||||
}
|
||||
|
||||
QTransform initialTransform;
|
||||
initialTransform.scale(fontDef.pixelSize, fontDef.pixelSize);
|
||||
boundingRect = initialTransform.mapRect(boundingRect);
|
||||
|
||||
QTransform originalXform = matrixToTransform(matrix);
|
||||
boundingRect = originalXform.mapRect(boundingRect);
|
||||
|
||||
qCDebug(lcColrv1).noquote() << "Bounds of"
|
||||
<< glyphRun->glyphIndices[0]
|
||||
<< " in device coordinates:"
|
||||
<< boundingRect;
|
||||
|
||||
QColrPaintGraphRenderer graphRenderer;
|
||||
graphRenderer.setBoundingRect(boundingRect);
|
||||
graphRenderer.beginRender(fontDef.pixelSize / m_unitsPerEm, matrixToTransform(matrix));
|
||||
if (!traverseColr1(paintReader.Get(), face7.Get(), &paintElement, &graphRenderer))
|
||||
return false;
|
||||
|
||||
*image = graphRenderer.endRender();
|
||||
} else {
|
||||
qCDebug(lcColrv1) << "Font face does not support IDWriteFontFace7 interface";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // QT_CONFIG(directwritecolrv1)
|
||||
|
||||
QImage QWindowsFontEngineDirectWrite::renderColorGlyph(DWRITE_GLYPH_RUN *glyphRun,
|
||||
const DWRITE_MATRIX &transform,
|
||||
DWRITE_RENDERING_MODE renderMode,
|
||||
@ -848,7 +1367,16 @@ QImage QWindowsFontEngineDirectWrite::renderColorGlyph(DWRITE_GLYPH_RUN *glyphRu
|
||||
{
|
||||
QImage ret;
|
||||
|
||||
#if QT_CONFIG(directwritecolrv1)
|
||||
// Start by trying COLRv1 glyph, where we need to get the paint nodes ourselves and render
|
||||
// them.
|
||||
renderColr1GlyphRun(&ret, glyphRun, transform, color);
|
||||
#endif // QT_CONFIG(directwritecolrv1)
|
||||
|
||||
#if QT_CONFIG(directwrite3)
|
||||
// If not successful, we try the modern API that supports both embedded pixmaps or COLRv0
|
||||
// glyphs, or a combination.
|
||||
if (ret.isNull()) {
|
||||
ComPtr<IDWriteFactory4> factory4;
|
||||
HRESULT hr = m_fontEngineData->directWriteFactory->QueryInterface(__uuidof(IDWriteFactory4),
|
||||
&factory4);
|
||||
@ -873,11 +1401,6 @@ QImage QWindowsFontEngineDirectWrite::renderColorGlyph(DWRITE_GLYPH_RUN *glyphRu
|
||||
NULL,
|
||||
0,
|
||||
&enumerator);
|
||||
if (FAILED(hr)) {
|
||||
qErrnoWarning(hr, "%s: TranslateGlyphRun failed", __FUNCTION__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
BOOL ok = true;
|
||||
while (SUCCEEDED(hr) && ok) {
|
||||
hr = enumerator->MoveNext(&ok);
|
||||
@ -893,7 +1416,7 @@ QImage QWindowsFontEngineDirectWrite::renderColorGlyph(DWRITE_GLYPH_RUN *glyphRu
|
||||
|
||||
if (colorGlyphRun->glyphImageFormat == DWRITE_GLYPH_IMAGE_FORMATS_NONE) {
|
||||
break;
|
||||
} else if (colorGlyphRun->glyphImageFormat & DWRITE_GLYPH_IMAGE_FORMATS_COLR) {
|
||||
} else if (colorGlyphRun->glyphImageFormat == DWRITE_GLYPH_IMAGE_FORMATS_COLR) {
|
||||
if (ret.isNull()) {
|
||||
ret = QImage(boundingRect.width() - 1,
|
||||
boundingRect.height() - 1,
|
||||
@ -964,8 +1487,10 @@ QImage QWindowsFontEngineDirectWrite::renderColorGlyph(DWRITE_GLYPH_RUN *glyphRu
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // QT_CONFIG(directwrite3)
|
||||
|
||||
// If all else fails, we go through the pre-dwrite3 version, which just supports COLRv0.
|
||||
if (ret.isNull()) {
|
||||
ComPtr<IDWriteFactory2> factory2;
|
||||
HRESULT hr = m_fontEngineData->directWriteFactory->QueryInterface(__uuidof(IDWriteFactory2),
|
||||
@ -1092,7 +1617,9 @@ QImage QWindowsFontEngineDirectWrite::imageForGlyph(glyph_t t,
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
QRect rect = alphaTextureBounds(t, transform);
|
||||
QRect rect = paintGraphBounds(t, transform);
|
||||
if (rect.isEmpty())
|
||||
rect = alphaTextureBounds(t, transform);
|
||||
if (rect.isEmpty())
|
||||
rect = colorBitmapBounds(t, transform);
|
||||
|
||||
@ -1467,8 +1994,12 @@ glyph_metrics_t QWindowsFontEngineDirectWrite::alphaMapBoundingBox(glyph_t glyph
|
||||
transform.m21 = matrix.m21();
|
||||
transform.m22 = matrix.m22();
|
||||
|
||||
// Try general approach first (works with regular truetype glyphs as well as COLRv0)
|
||||
QRect rect = alphaTextureBounds(glyph, transform);
|
||||
// Try COLRv1 approach first
|
||||
QRect rect = paintGraphBounds(glyph, transform);
|
||||
|
||||
// Then try general approach (works with regular truetype glyphs as well as COLRv0)
|
||||
if (rect.isEmpty())
|
||||
rect = alphaTextureBounds(glyph, transform);
|
||||
|
||||
// If this fails, we check if it is an embedded color bitmap
|
||||
if (rect.isEmpty())
|
||||
|
@ -34,9 +34,16 @@ struct IDWriteGdiInterop;
|
||||
struct IDWriteGlyphRunAnalysis;
|
||||
struct IDWriteColorGlyphRunEnumerator;
|
||||
|
||||
#if QT_CONFIG(directwritecolrv1)
|
||||
struct IDWriteFontFace7;
|
||||
struct IDWritePaintReader;
|
||||
struct DWRITE_PAINT_ELEMENT;
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QWindowsFontEngineData;
|
||||
class QColrPaintGraphRenderer;
|
||||
|
||||
class Q_GUI_EXPORT QWindowsFontEngineDirectWrite : public QFontEngine
|
||||
{
|
||||
@ -102,11 +109,25 @@ public:
|
||||
void initializeHeightMetrics() const override;
|
||||
|
||||
Properties properties() const override;
|
||||
|
||||
QPainterPath unscaledGlyph(glyph_t glyph) const;
|
||||
void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) override;
|
||||
|
||||
QList<QFontVariableAxis> variableAxes() const override;
|
||||
|
||||
private:
|
||||
#if QT_CONFIG(directwritecolrv1)
|
||||
bool traverseColr1(IDWritePaintReader *paintReader,
|
||||
IDWriteFontFace7 *face7,
|
||||
const DWRITE_PAINT_ELEMENT *paintElement,
|
||||
QColrPaintGraphRenderer *graphRenderer) const;
|
||||
bool renderColr1GlyphRun(QImage *image,
|
||||
const DWRITE_GLYPH_RUN *glyphRun,
|
||||
const DWRITE_MATRIX &transform,
|
||||
QColor color) const;
|
||||
#endif
|
||||
|
||||
QRect paintGraphBounds(glyph_t glyph, const DWRITE_MATRIX &transform) const;
|
||||
QRect alphaTextureBounds(glyph_t glyph, const DWRITE_MATRIX &transform);
|
||||
QRect colorBitmapBounds(glyph_t glyph, const DWRITE_MATRIX &transform);
|
||||
bool renderColr0GlyphRun(QImage *image,
|
||||
|
Loading…
x
Reference in New Issue
Block a user