From 0c4b517e6ea90a5996de10a41cb01a51bf50012c Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Wed, 22 Jan 2025 15:05:41 +0100 Subject: [PATCH] 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 (cherry picked from commit 9f22a51987614ce51b3c12ced420bc4ca5b1ec61) Reviewed-by: Qt Cherry-pick Bot --- src/gui/CMakeLists.txt | 1 + src/gui/configure.cmake | 22 + src/gui/text/freetype/qfontengine_ft.cpp | 4 - src/gui/text/qcolrpaintgraphrenderer.cpp | 316 ++++++++ src/gui/text/qcolrpaintgraphrenderer_p.h | 81 ++ src/gui/text/qfontengine.cpp | 2 + src/gui/text/qfontengine_p.h | 2 + .../windows/qwindowsfontenginedirectwrite.cpp | 751 +++++++++++++++--- .../windows/qwindowsfontenginedirectwrite_p.h | 21 + 9 files changed, 1086 insertions(+), 114 deletions(-) create mode 100644 src/gui/text/qcolrpaintgraphrenderer.cpp create mode 100644 src/gui/text/qcolrpaintgraphrenderer_p.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index e6c9875ce2f..dd54e567c98 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -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 diff --git a/src/gui/configure.cmake b/src/gui/configure.cmake index b7eb37e8a8c..a42f453e4a5 100644 --- a/src/gui/configure.cmake +++ b/src/gui/configure.cmake @@ -623,6 +623,22 @@ int main(int, char **) } ") +# directwritecolrv1 +qt_config_compile_test(directwritecolrv1 + LABEL "WINDOWS directwritecolrv1" + LIBRARIES + dwrite + CODE +"#include +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( diff --git a/src/gui/text/freetype/qfontengine_ft.cpp b/src/gui/text/freetype/qfontengine_ft.cpp index 8229d06518b..92ca088306b 100644 --- a/src/gui/text/freetype/qfontengine_ft.cpp +++ b/src/gui/text/freetype/qfontengine_ft.cpp @@ -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) diff --git a/src/gui/text/qcolrpaintgraphrenderer.cpp b/src/gui/text/qcolrpaintgraphrenderer.cpp new file mode 100644 index 00000000000..9041e804753 --- /dev/null +++ b/src/gui/text/qcolrpaintgraphrenderer.cpp @@ -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 diff --git a/src/gui/text/qcolrpaintgraphrenderer_p.h b/src/gui/text/qcolrpaintgraphrenderer_p.h new file mode 100644 index 00000000000..4e45fcb8bd4 --- /dev/null +++ b/src/gui/text/qcolrpaintgraphrenderer_p.h @@ -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 +#include +#include + +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 m_oldPaths; + QList m_oldTransforms; +}; + +QT_END_NAMESPACE + +#endif // QCOLRPAINTGRAPHRENDERER_P_H diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index f3c768d6bc7..d4eeee5aefa 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -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) diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h index 654c3323609..032a8b78126 100644 --- a/src/gui/text/qfontengine_p.h +++ b/src/gui/text/qfontengine_p.h @@ -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: diff --git a/src/gui/text/windows/qwindowsfontenginedirectwrite.cpp b/src/gui/text/windows/qwindowsfontenginedirectwrite.cpp index 23636c7bd91..cf5f530f8ae 100644 --- a/src/gui/text/windows/qwindowsfontenginedirectwrite.cpp +++ b/src/gui/text/windows/qwindowsfontenginedirectwrite.cpp @@ -15,6 +15,8 @@ #include #include +#include + #if QT_CONFIG(directwrite3) # include "qwindowsdirectwritefontdatabase_p.h" # include @@ -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 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 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 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 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 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,124 +1367,130 @@ 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) - ComPtr factory4; - HRESULT hr = m_fontEngineData->directWriteFactory->QueryInterface(__uuidof(IDWriteFactory4), - &factory4); - if (SUCCEEDED(hr)) { - const DWRITE_GLYPH_IMAGE_FORMATS supportedBitmapFormats = - DWRITE_GLYPH_IMAGE_FORMATS(DWRITE_GLYPH_IMAGE_FORMATS_PNG - | DWRITE_GLYPH_IMAGE_FORMATS_JPEG - | DWRITE_GLYPH_IMAGE_FORMATS_TIFF); + // If not successful, we try the modern API that supports both embedded pixmaps or COLRv0 + // glyphs, or a combination. + if (ret.isNull()) { + ComPtr factory4; + HRESULT hr = m_fontEngineData->directWriteFactory->QueryInterface(__uuidof(IDWriteFactory4), + &factory4); + if (SUCCEEDED(hr)) { + const DWRITE_GLYPH_IMAGE_FORMATS supportedBitmapFormats = + DWRITE_GLYPH_IMAGE_FORMATS(DWRITE_GLYPH_IMAGE_FORMATS_PNG + | DWRITE_GLYPH_IMAGE_FORMATS_JPEG + | DWRITE_GLYPH_IMAGE_FORMATS_TIFF); - const DWRITE_GLYPH_IMAGE_FORMATS glyphFormats = - DWRITE_GLYPH_IMAGE_FORMATS(DWRITE_GLYPH_IMAGE_FORMATS_COLR - | supportedBitmapFormats - | DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE - | DWRITE_GLYPH_IMAGE_FORMATS_CFF); + const DWRITE_GLYPH_IMAGE_FORMATS glyphFormats = + DWRITE_GLYPH_IMAGE_FORMATS(DWRITE_GLYPH_IMAGE_FORMATS_COLR + | supportedBitmapFormats + | DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE + | DWRITE_GLYPH_IMAGE_FORMATS_CFF); - ComPtr enumerator; - hr = factory4->TranslateColorGlyphRun(D2D1::Point2F(0.0f, 0.0f), - glyphRun, - NULL, - glyphFormats, - measureMode, - NULL, - 0, - &enumerator); - if (FAILED(hr)) { - qErrnoWarning(hr, "%s: TranslateGlyphRun failed", __FUNCTION__); - return ret; - } + ComPtr enumerator; + hr = factory4->TranslateColorGlyphRun(D2D1::Point2F(0.0f, 0.0f), + glyphRun, + NULL, + glyphFormats, + measureMode, + NULL, + 0, + &enumerator); + BOOL ok = true; + while (SUCCEEDED(hr) && ok) { + hr = enumerator->MoveNext(&ok); + if (!ok) + break; - BOOL ok = true; - while (SUCCEEDED(hr) && ok) { - hr = enumerator->MoveNext(&ok); - if (!ok) - break; - - const DWRITE_COLOR_GLYPH_RUN1 *colorGlyphRun = nullptr; - hr = enumerator->GetCurrentRun(&colorGlyphRun); - if (FAILED(hr)) { - qErrnoWarning(hr, "%s: IDWriteColorGlyphRunEnumerator::GetCurrentRun failed", __FUNCTION__); - return QImage{}; - } - - if (colorGlyphRun->glyphImageFormat == DWRITE_GLYPH_IMAGE_FORMATS_NONE) { - break; - } else if (colorGlyphRun->glyphImageFormat & DWRITE_GLYPH_IMAGE_FORMATS_COLR) { - if (ret.isNull()) { - ret = QImage(boundingRect.width() - 1, - boundingRect.height() - 1, - QImage::Format_ARGB32_Premultiplied); - ret.fill(0); - } - - if (!renderColr0GlyphRun(&ret, - reinterpret_cast(colorGlyphRun), // Broken inheritance in MinGW - transform, - renderMode, - measureMode, - gridFitMode, - color, - boundingRect)) { + const DWRITE_COLOR_GLYPH_RUN1 *colorGlyphRun = nullptr; + hr = enumerator->GetCurrentRun(&colorGlyphRun); + if (FAILED(hr)) { + qErrnoWarning(hr, "%s: IDWriteColorGlyphRunEnumerator::GetCurrentRun failed", __FUNCTION__); return QImage{}; } - } else if (colorGlyphRun->glyphImageFormat & supportedBitmapFormats) { - ComPtr face4; - if (SUCCEEDED(m_directWriteFontFace->QueryInterface(__uuidof(IDWriteFontFace4), - &face4))) { - DWRITE_GLYPH_IMAGE_DATA data; - void *ctx; - Q_ASSERT(glyphRun->glyphCount == 1); - HRESULT hr = face4->GetGlyphImageData(glyphRun->glyphIndices[0], - fontDef.pixelSize, - DWRITE_GLYPH_IMAGE_FORMATS(colorGlyphRun->glyphImageFormat & supportedBitmapFormats), - &data, - &ctx); - if (FAILED(hr)) { - qErrnoWarning("%s: GetGlyphImageData failed", __FUNCTION__); - return QImage{}; + + if (colorGlyphRun->glyphImageFormat == DWRITE_GLYPH_IMAGE_FORMATS_NONE) { + break; + } else if (colorGlyphRun->glyphImageFormat == DWRITE_GLYPH_IMAGE_FORMATS_COLR) { + if (ret.isNull()) { + ret = QImage(boundingRect.width() - 1, + boundingRect.height() - 1, + QImage::Format_ARGB32_Premultiplied); + ret.fill(0); } - const char *format; - switch (colorGlyphRun->glyphImageFormat) { - case DWRITE_GLYPH_IMAGE_FORMATS_JPEG: - format = "JPEG"; - break; - case DWRITE_GLYPH_IMAGE_FORMATS_TIFF: - format = "TIFF"; - break; - default: - format = "PNG"; - break; - }; + if (!renderColr0GlyphRun(&ret, + reinterpret_cast(colorGlyphRun), // Broken inheritance in MinGW + transform, + renderMode, + measureMode, + gridFitMode, + color, + boundingRect)) { + return QImage{}; + } + } else if (colorGlyphRun->glyphImageFormat & supportedBitmapFormats) { + ComPtr face4; + if (SUCCEEDED(m_directWriteFontFace->QueryInterface(__uuidof(IDWriteFontFace4), + &face4))) { + DWRITE_GLYPH_IMAGE_DATA data; + void *ctx; + Q_ASSERT(glyphRun->glyphCount == 1); + HRESULT hr = face4->GetGlyphImageData(glyphRun->glyphIndices[0], + fontDef.pixelSize, + DWRITE_GLYPH_IMAGE_FORMATS(colorGlyphRun->glyphImageFormat & supportedBitmapFormats), + &data, + &ctx); + if (FAILED(hr)) { + qErrnoWarning("%s: GetGlyphImageData failed", __FUNCTION__); + return QImage{}; + } - ret = QImage::fromData(reinterpret_cast(data.imageData), - data.imageDataSize, - format); + const char *format; + switch (colorGlyphRun->glyphImageFormat) { + case DWRITE_GLYPH_IMAGE_FORMATS_JPEG: + format = "JPEG"; + break; + case DWRITE_GLYPH_IMAGE_FORMATS_TIFF: + format = "TIFF"; + break; + default: + format = "PNG"; + break; + }; - QTransform matrix(transform.m11, transform.m12, - transform.m21, transform.m22, - transform.dx, transform.dy); + ret = QImage::fromData(reinterpret_cast(data.imageData), + data.imageDataSize, + format); - const qreal scale = fontDef.pixelSize / data.pixelsPerEm; - matrix.scale(scale, scale); + QTransform matrix(transform.m11, transform.m12, + transform.m21, transform.m22, + transform.dx, transform.dy); - if (!matrix.isIdentity()) - ret = ret.transformed(matrix, Qt::SmoothTransformation); + const qreal scale = fontDef.pixelSize / data.pixelsPerEm; + matrix.scale(scale, scale); - face4->ReleaseGlyphImageData(ctx); + if (!matrix.isIdentity()) + ret = ret.transformed(matrix, Qt::SmoothTransformation); + + face4->ReleaseGlyphImageData(ctx); + } + + } else { + qCDebug(lcQpaFonts) << "Found glyph run with unsupported format" + << colorGlyphRun->glyphImageFormat; } - - } else { - qCDebug(lcQpaFonts) << "Found glyph run with unsupported format" - << colorGlyphRun->glyphImageFormat; } } } #endif // QT_CONFIG(directwrite3) + // If all else fails, we go through the pre-dwrite3 version, which just supports COLRv0. if (ret.isNull()) { ComPtr 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()) diff --git a/src/gui/text/windows/qwindowsfontenginedirectwrite_p.h b/src/gui/text/windows/qwindowsfontenginedirectwrite_p.h index 2c1e844a579..d00558a26fa 100644 --- a/src/gui/text/windows/qwindowsfontenginedirectwrite_p.h +++ b/src/gui/text/windows/qwindowsfontenginedirectwrite_p.h @@ -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 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,