Port Freetype engine to COLR paint graph renderer

Use the cross-platform renderer code in Freetype as well as
DirectWrite to share as much code as possible.

Change-Id: I643dced6b913138890b39eafc3984cca5ced0aae
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
(cherry picked from commit d58ad990e227fecf5526df1a89736afc0c9268fa)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Eskil Abrahamsen Blomfeldt 2025-01-23 15:52:36 +01:00 committed by Qt Cherry-pick Bot
parent 0c4b517e6e
commit 8d791bfb30
2 changed files with 155 additions and 349 deletions

View File

@ -24,6 +24,7 @@
#include "qthreadstorage.h" #include "qthreadstorage.h"
#include <qmath.h> #include <qmath.h>
#include <qendian.h> #include <qendian.h>
#include <private/qcolrpaintgraphrenderer_p.h>
#include <memory> #include <memory>
@ -1187,29 +1188,25 @@ static inline QTransform FTAffineToQTransform(const FT_Affine23 &matrix)
} }
bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint, bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
Colr1PaintInfo *paintInfo) const QSet<QPair<FT_Byte *, FT_Bool> > *loops,
QColor foregroundColor,
FT_Color *palette,
ushort paletteCount,
QColrPaintGraphRenderer *paintGraphRenderer) const
{ {
FT_Face face = freetype->face; FT_Face face = freetype->face;
auto key = qMakePair(opaquePaint.p, opaquePaint.insert_root_transform); auto key = qMakePair(opaquePaint.p, opaquePaint.insert_root_transform);
if (paintInfo->loops.contains(key)) { if (loops->contains(key)) {
qCWarning(lcColrv1) << "Cycle detected in COLRv1 graph"; qCWarning(lcColrv1) << "Cycle detected in COLRv1 graph";
return false; return false;
} }
if (paintInfo->painter != nullptr) paintGraphRenderer->save();
paintInfo->painter->save(); loops->insert(key);
auto cleanup = qScopeGuard([&paintGraphRenderer, &key, &loops]() {
QTransform oldTransform = paintInfo->transform; loops->remove(key);
QPainterPath oldPath = paintInfo->currentPath; paintGraphRenderer->restore();
paintInfo->loops.insert(key);
auto cleanup = qScopeGuard([&paintInfo, &key, &oldTransform, &oldPath]() {
paintInfo->loops.remove(key);
paintInfo->transform = oldTransform;
paintInfo->currentPath = oldPath;
if (paintInfo->painter != nullptr)
paintInfo->painter->restore();
}); });
FT_COLR_Paint paint; FT_COLR_Paint paint;
@ -1217,14 +1214,10 @@ bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
return false; return false;
if (paint.format == FT_COLR_PAINTFORMAT_COLR_LAYERS) { if (paint.format == FT_COLR_PAINTFORMAT_COLR_LAYERS) {
qCDebug(lcColrv1).noquote().nospace()
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[layers]";
FT_OpaquePaint layerPaint; FT_OpaquePaint layerPaint;
layerPaint.p = nullptr; layerPaint.p = nullptr;
while (FT_Get_Paint_Layers(face, &paint.u.colr_layers.layer_iterator, &layerPaint)) { while (FT_Get_Paint_Layers(face, &paint.u.colr_layers.layer_iterator, &layerPaint)) {
if (!traverseColr1(layerPaint, paintInfo)) if (!traverseColr1(layerPaint, loops, foregroundColor, palette, paletteCount, paintGraphRenderer))
return false; return false;
} }
} else if (paint.format == FT_COLR_PAINTFORMAT_TRANSFORM } else if (paint.format == FT_COLR_PAINTFORMAT_TRANSFORM
@ -1239,11 +1232,6 @@ bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
case FT_COLR_PAINTFORMAT_TRANSFORM: case FT_COLR_PAINTFORMAT_TRANSFORM:
xform = FTAffineToQTransform(paint.u.transform.affine); xform = FTAffineToQTransform(paint.u.transform.affine);
nextPaint = paint.u.transform.paint; nextPaint = paint.u.transform.paint;
qCDebug(lcColrv1).noquote().nospace()
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[transform " << xform << "]";
break; break;
case FT_COLR_PAINTFORMAT_SCALE: case FT_COLR_PAINTFORMAT_SCALE:
{ {
@ -1257,11 +1245,6 @@ bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
xform.translate(-centerX, -centerY); xform.translate(-centerX, -centerY);
nextPaint = paint.u.scale.paint; nextPaint = paint.u.scale.paint;
qCDebug(lcColrv1).noquote().nospace()
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[scale " << xform << "]";
break; break;
} }
case FT_COLR_PAINTFORMAT_ROTATE: case FT_COLR_PAINTFORMAT_ROTATE:
@ -1275,11 +1258,6 @@ bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
xform.translate(-centerX, -centerY); xform.translate(-centerX, -centerY);
nextPaint = paint.u.rotate.paint; nextPaint = paint.u.rotate.paint;
qCDebug(lcColrv1).noquote().nospace()
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[rotate " << xform << "]";
break; break;
} }
@ -1295,11 +1273,6 @@ bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
xform.translate(-centerX, -centerY); xform.translate(-centerX, -centerY);
nextPaint = paint.u.rotate.paint; nextPaint = paint.u.rotate.paint;
qCDebug(lcColrv1).noquote().nospace()
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[skew " << xform << "]";
break; break;
} }
case FT_COLR_PAINTFORMAT_TRANSLATE: case FT_COLR_PAINTFORMAT_TRANSLATE:
@ -1309,46 +1282,30 @@ bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
xform.translate(dx, dy); xform.translate(dx, dy);
nextPaint = paint.u.rotate.paint; nextPaint = paint.u.rotate.paint;
qCDebug(lcColrv1).noquote().nospace()
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[translate " << xform << "]";
break; break;
} }
default: default:
Q_UNREACHABLE(); Q_UNREACHABLE();
}; };
paintInfo->transform = xform * paintInfo->transform; paintGraphRenderer->prependTransform(xform);
if (!traverseColr1(nextPaint, paintInfo)) if (!traverseColr1(nextPaint, loops, foregroundColor, palette, paletteCount, paintGraphRenderer))
return false; return false;
} else if (paint.format == FT_COLR_PAINTFORMAT_LINEAR_GRADIENT } else if (paint.format == FT_COLR_PAINTFORMAT_LINEAR_GRADIENT
|| paint.format == FT_COLR_PAINTFORMAT_RADIAL_GRADIENT || paint.format == FT_COLR_PAINTFORMAT_RADIAL_GRADIENT
|| paint.format == FT_COLR_PAINTFORMAT_SWEEP_GRADIENT || paint.format == FT_COLR_PAINTFORMAT_SWEEP_GRADIENT
|| paint.format == FT_COLR_PAINTFORMAT_SOLID) { || paint.format == FT_COLR_PAINTFORMAT_SOLID) {
QRect boundingRect = paintInfo->currentPath.boundingRect().toAlignedRect(); auto getPaletteColor = [&palette, &paletteCount, &foregroundColor](FT_UInt16 index,
if (paintInfo->painter == nullptr) { FT_F2Dot14 alpha) {
paintInfo->boundingRect = paintInfo->boundingRect.isValid()
? paintInfo->boundingRect.united(boundingRect)
: boundingRect;
}
qCDebug(lcColrv1).noquote().nospace()
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[fill " << paint.format << "]";
if (paintInfo->painter != nullptr && !paintInfo->currentPath.isEmpty() && !boundingRect.isEmpty()) {
auto getPaletteColor = [&paintInfo](FT_UInt16 index, FT_F2Dot14 alpha) {
QColor color; QColor color;
if (index < paintInfo->paletteCount) { if (index < paletteCount) {
const FT_Color &paletteColor = paintInfo->palette[index]; const FT_Color &paletteColor = palette[index];
color = qRgba(paletteColor.red, color = qRgba(paletteColor.red,
paletteColor.green, paletteColor.green,
paletteColor.blue, paletteColor.blue,
paletteColor.alpha); paletteColor.alpha);
} else if (index == 0xffff) { } else if (index == 0xffff) {
color = paintInfo->foregroundColor; color = foregroundColor;
} }
if (color.isValid()) if (color.isValid())
@ -1386,6 +1343,7 @@ bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
} }
}; };
if (paintGraphRenderer->isRendering()) {
if (paint.format == FT_COLR_PAINTFORMAT_LINEAR_GRADIENT) { if (paint.format == FT_COLR_PAINTFORMAT_LINEAR_GRADIENT) {
const qreal p0x = FROM_FIXED_16_16(paint.u.linear_gradient.p0.x); const qreal p0x = FROM_FIXED_16_16(paint.u.linear_gradient.p0.x);
const qreal p0y = -FROM_FIXED_16_16(paint.u.linear_gradient.p0.y); const qreal p0y = -FROM_FIXED_16_16(paint.u.linear_gradient.p0.y);
@ -1400,61 +1358,11 @@ bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
QPointF p1(p1x, p1y); QPointF p1(p1x, p1y);
QPointF p2(p2x, p2y); QPointF p2(p2x, p2y);
qCDebug(lcColrv1).noquote().nospace() const QGradient::Spread spread =
<< QByteArray().fill(' ', paintInfo->loops.size() * 2) extendToSpread(paint.u.linear_gradient.colorline.extend);
<< "[linear gradient " << p0 << ", " << p1 << ", " << p2 << "]"; const QGradientStops stops =
gatherGradientStops(paint.u.linear_gradient.colorline.color_stop_iterator);
// Calculate new start and end point for single vector gradient preferred by Qt paintGraphRenderer->setLinearGradient(p0, p1, p2, spread, stops);
// 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 = QVector2D(p2 - p0);
if (qFuzzyIsNull(p0p2.lengthSquared())) {
qCWarning(lcColrv1) << "Malformed linear gradient in COLRv1 graph. Points:"
<< p0
<< p1
<< p2;
return false;
}
// v is perpendicular to p0p2
QVector2D v = QVector2D(p0p2.y(), -p0p2.x());
// u is the vector from p0 to p1
QVector2D u = QVector2D(p1 - p0);
if (qFuzzyIsNull(u.lengthSquared())) {
qCWarning(lcColrv1) << "Malformed linear gradient in COLRv1 graph. Points:"
<< p0
<< p1
<< p2;
return false;
}
// We find the projected point p3
QPointF p3 = (QVector2D(p0) + v * QVector2D::dotProduct(u, v) / v.lengthSquared()).toPointF();
p0 = paintInfo->transform.map(p0);
p3 = paintInfo->transform.map(p1);
// Convert to normalized object coordinates
QRectF brect = paintInfo->currentPath.boundingRect();
if (brect.isEmpty())
return false;
p0 -= brect.topLeft();
p3 -= brect.topLeft();
p0.rx() /= brect.width();
p0.ry() /= brect.height();
p3.rx() /= brect.width();
p3.ry() /= brect.height();
QLinearGradient linearGradient(p0, p3);
linearGradient.setSpread(extendToSpread(paint.u.linear_gradient.colorline.extend));
linearGradient.setStops(gatherGradientStops(paint.u.linear_gradient.colorline.color_stop_iterator));
linearGradient.setCoordinateMode(QGradient::ObjectMode);
paintInfo->painter->setBrush(linearGradient);
} else if (paint.format == FT_COLR_PAINTFORMAT_RADIAL_GRADIENT) { } else if (paint.format == FT_COLR_PAINTFORMAT_RADIAL_GRADIENT) {
const qreal c0x = FROM_FIXED_16_16(paint.u.radial_gradient.c0.x); const qreal c0x = FROM_FIXED_16_16(paint.u.radial_gradient.c0.x);
@ -1464,154 +1372,60 @@ bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
const qreal c1y = -FROM_FIXED_16_16(paint.u.radial_gradient.c1.y); const qreal c1y = -FROM_FIXED_16_16(paint.u.radial_gradient.c1.y);
const qreal r1 = FROM_FIXED_16_16(paint.u.radial_gradient.r1); const qreal r1 = FROM_FIXED_16_16(paint.u.radial_gradient.r1);
QPointF c0(c0x, c0y); const QPointF c0(c0x, c0y);
QPointF c1(c1x, c1y); const QPointF c1(c1x, c1y);
QPointF c0e(c0x + r0, c0y); const QGradient::Spread spread =
QPointF c1e(c1x + r1, c1y); extendToSpread(paint.u.radial_gradient.colorline.extend);
const QGradientStops stops =
gatherGradientStops(paint.u.radial_gradient.colorline.color_stop_iterator);
c0 = paintInfo->transform.map(c0); paintGraphRenderer->setRadialGradient(c0, r0, c1, r1, spread, stops);
c1 = paintInfo->transform.map(c1);
c0e = paintInfo->transform.map(c0e);
c1e = paintInfo->transform.map(c1e);
// Convert to normalized object coordinates
QRectF brect = paintInfo->currentPath.boundingRect();
if (brect.isEmpty())
return false;
c0 -= brect.topLeft();
c1 -= brect.topLeft();
c0.rx() /= brect.width();
c0.ry() /= brect.height();
c1.rx() /= brect.width();
c1.ry() /= brect.height();
c0e -= brect.topLeft();
c1e -= brect.topLeft();
c0e.rx() /= brect.width();
c0e.ry() /= brect.height();
c1e.rx() /= brect.width();
c1e.ry() /= brect.height();
QVector2D d0 = QVector2D(c0e - c0);
QVector2D d1 = QVector2D(c1e - c1);
// c1 is center of gradient and c0 is the focal point
// https://learn.microsoft.com/en-us/typography/opentype/spec/colr#radial-gradients
QRadialGradient radialGradient(c1, d1.length(), c0, d0.length());
radialGradient.setSpread(extendToSpread(paint.u.radial_gradient.colorline.extend));
radialGradient.setStops(gatherGradientStops(paint.u.radial_gradient.colorline.color_stop_iterator));
radialGradient.setCoordinateMode(QGradient::ObjectMode);
qCDebug(lcColrv1).noquote().nospace()
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[radial gradient " << c0 << ", rad=" << r0 << ", " << c1 << ", rad=" << r1 << "]";
paintInfo->painter->setBrush(radialGradient);
} else if (paint.format == FT_COLR_PAINTFORMAT_SWEEP_GRADIENT) { } else if (paint.format == FT_COLR_PAINTFORMAT_SWEEP_GRADIENT) {
qreal centerX = FROM_FIXED_16_16(paint.u.sweep_gradient.center.x); const qreal centerX = FROM_FIXED_16_16(paint.u.sweep_gradient.center.x);
qreal centerY = -FROM_FIXED_16_16(paint.u.sweep_gradient.center.y); const qreal centerY = -FROM_FIXED_16_16(paint.u.sweep_gradient.center.y);
qreal startAngle = 180.0 * FROM_FIXED_16_16(paint.u.sweep_gradient.start_angle); const qreal startAngle = 180.0 * FROM_FIXED_16_16(paint.u.sweep_gradient.start_angle);
qreal endAngle = 180.0 * FROM_FIXED_16_16(paint.u.sweep_gradient.end_angle); const qreal endAngle = 180.0 * FROM_FIXED_16_16(paint.u.sweep_gradient.end_angle);
qCDebug(lcColrv1).noquote().nospace() const QPointF center(centerX, centerY);
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[sweep gradient " << "(" << centerX << ", " << centerY << ") range: " << startAngle << " to " << endAngle << "]";
QPointF center(centerX, centerY); const QGradient::Spread spread = extendToSpread(paint.u.radial_gradient.colorline.extend);
QRectF brect = paintInfo->currentPath.boundingRect(); const QGradientStops stops = gatherGradientStops(paint.u.sweep_gradient.colorline.color_stop_iterator);
if (brect.isEmpty())
return false;
center -= brect.topLeft(); paintGraphRenderer->setConicalGradient(center, startAngle, endAngle, spread, stops);
center.rx() /= brect.width();
center.ry() /= brect.height();
QConicalGradient conicalGradient(centerX, centerY, startAngle);
conicalGradient.setSpread(extendToSpread(paint.u.radial_gradient.colorline.extend));
// 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 stops = gatherGradientStops(paint.u.sweep_gradient.colorline.color_stop_iterator);
for (QGradientStop &stop : stops)
stop.first *= multiplier;
conicalGradient.setStops(stops);
conicalGradient.setCoordinateMode(QGradient::ObjectMode);
} else if (paint.format == FT_COLR_PAINTFORMAT_SOLID) { } else if (paint.format == FT_COLR_PAINTFORMAT_SOLID) {
QColor color = getPaletteColor(paint.u.solid.color.palette_index, QColor color = getPaletteColor(paint.u.solid.color.palette_index,
paint.u.solid.color.alpha); paint.u.solid.color.alpha);
qCDebug(lcColrv1).noquote().nospace()
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[solid fill " << color << "]";
if (!color.isValid()) { if (!color.isValid()) {
qCWarning(lcColrv1) << "Invalid palette index in COLRv1 graph:" qCWarning(lcColrv1) << "Invalid palette index in COLRv1 graph:"
<< paint.u.solid.color.palette_index; << paint.u.solid.color.palette_index;
return false; return false;
} }
paintInfo->painter->setBrush(color); paintGraphRenderer->setSolidColor(color);
}
} }
qCDebug(lcColrv1).noquote().nospace() paintGraphRenderer->drawCurrentPath();
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[drawing path with " << paintInfo->transform << ", bounds == " << boundingRect << "]";
paintInfo->painter->drawPath(paintInfo->currentPath);
}
} else if (paint.format == FT_COLR_PAINTFORMAT_COMPOSITE) { } else if (paint.format == FT_COLR_PAINTFORMAT_COMPOSITE) {
qCDebug(lcColrv1).noquote().nospace() if (!paintGraphRenderer->isRendering()) {
<< QByteArray().fill(' ', paintInfo->loops.size() * 2) if (!traverseColr1(paint.u.composite.backdrop_paint,
<< "[composite " << paint.u.composite.composite_mode << "]"; loops,
foregroundColor,
if (paintInfo->painter == nullptr) { palette,
if (!traverseColr1(paint.u.composite.backdrop_paint, paintInfo)) paletteCount,
paintGraphRenderer)) {
return false; return false;
if (!traverseColr1(paint.u.composite.source_paint, paintInfo)) }
if (!traverseColr1(paint.u.composite.source_paint,
loops,
foregroundColor,
palette,
paletteCount,
paintGraphRenderer)) {
return false; return false;
}
} else { } else {
Colr1PaintInfo compositePaintInfo;
compositePaintInfo.transform = paintInfo->transform;
compositePaintInfo.foregroundColor = paintInfo->foregroundColor;
compositePaintInfo.paletteCount = paintInfo->paletteCount;
compositePaintInfo.palette = paintInfo->palette;
compositePaintInfo.boundingRect = paintInfo->boundingRect;
compositePaintInfo.designCoordinateBoundingRect = paintInfo->designCoordinateBoundingRect;
QImage composedImage(compositePaintInfo.boundingRect.size(), QImage::Format_ARGB32_Premultiplied);
composedImage.fill(Qt::transparent);
QPainter compositePainter;
compositePainter.begin(&composedImage);
compositePainter.setRenderHint(QPainter::Antialiasing);
compositePainter.setPen(Qt::NoPen);
compositePainter.setBrush(Qt::NoBrush);
compositePainter.translate(-compositePaintInfo.boundingRect.left(),
-compositePaintInfo.boundingRect.top());
compositePainter.scale(fontDef.pixelSize / face->units_per_EM, fontDef.pixelSize / face->units_per_EM);
compositePaintInfo.painter = &compositePainter;
// First we draw the back drop onto the composed image
if (!traverseColr1(paint.u.composite.backdrop_paint, &compositePaintInfo))
return false;
QPainter::CompositionMode compositionMode = QPainter::CompositionMode_SourceOver; QPainter::CompositionMode compositionMode = QPainter::CompositionMode_SourceOver;
switch (paint.u.composite.composite_mode) { switch (paint.u.composite.composite_mode) {
case FT_COLR_COMPOSITE_CLEAR: case FT_COLR_COMPOSITE_CLEAR:
@ -1691,14 +1505,29 @@ bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
break; break;
}; };
// Then we composite the source_paint on top QColrPaintGraphRenderer compositeRenderer;
compositePainter.setCompositionMode(compositionMode); compositeRenderer.setBoundingRect(paintGraphRenderer->boundingRect());
if (!traverseColr1(paint.u.composite.source_paint, &compositePaintInfo)) compositeRenderer.beginRender(fontDef.pixelSize / face->units_per_EM,
paintGraphRenderer->currentTransform());
if (!traverseColr1(paint.u.composite.backdrop_paint,
loops,
foregroundColor,
palette,
paletteCount,
&compositeRenderer)) {
return false; return false;
compositePainter.end(); }
// Finally, we draw the composed image compositeRenderer.setCompositionMode(compositionMode);
paintInfo->painter->drawImage(paintInfo->designCoordinateBoundingRect, composedImage); if (!traverseColr1(paint.u.composite.source_paint,
loops,
foregroundColor,
palette,
paletteCount,
&compositeRenderer)) {
return false;
}
paintGraphRenderer->drawImage(compositeRenderer.endRender());
} }
} else if (paint.format == FT_COLR_PAINTFORMAT_GLYPH) { } else if (paint.format == FT_COLR_PAINTFORMAT_GLYPH) {
FT_Error error = FT_Load_Glyph(face, FT_Error error = FT_Load_Glyph(face,
@ -1719,21 +1548,11 @@ bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
face->units_per_EM << 6, face->units_per_EM << 6,
face->units_per_EM << 6); face->units_per_EM << 6);
qCDebug(lcColrv1).noquote().nospace() paintGraphRenderer->appendPath(path);
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[glyph " << paint.u.glyph.glyphID << " bounds = " << path.boundingRect() << "; mapped = " << paintInfo->transform.mapRect(path.boundingRect()) << "]";
path = paintInfo->transform.map(path); if (!traverseColr1(paint.u.glyph.paint, loops, foregroundColor, palette, paletteCount, paintGraphRenderer))
// For sequences of paths, merge them and fill them all once we reach the fill node
paintInfo->currentPath = paintInfo->currentPath.isEmpty() ? path : paintInfo->currentPath.united(path);
if (!traverseColr1(paint.u.glyph.paint, paintInfo))
return false; return false;
} else if (paint.format == FT_COLR_PAINTFORMAT_COLR_GLYPH) { } else if (paint.format == FT_COLR_PAINTFORMAT_COLR_GLYPH) {
qCDebug(lcColrv1).noquote().nospace()
<< QByteArray().fill(' ', paintInfo->loops.size() * 2)
<< "[colr glyph " << paint.u.colr_glyph.glyphID << "]";
FT_OpaquePaint otherOpaquePaint; FT_OpaquePaint otherOpaquePaint;
otherOpaquePaint.p = nullptr; otherOpaquePaint.p = nullptr;
if (!FT_Get_Color_Glyph_Paint(face, if (!FT_Get_Color_Glyph_Paint(face,
@ -1746,7 +1565,7 @@ bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
return false; return false;
} }
if (!traverseColr1(otherOpaquePaint, paintInfo)) if (!traverseColr1(otherOpaquePaint, loops, foregroundColor, palette, paletteCount, paintGraphRenderer))
return false; return false;
} }
@ -1810,7 +1629,7 @@ QFontEngineFT::Glyph *QFontEngineFT::loadColrv1Glyph(QGlyphSet *set,
// COLRv1 fonts can optionally have a clip box for quicker retrieval of metrics. We try // COLRv1 fonts can optionally have a clip box for quicker retrieval of metrics. We try
// to get this, and if there is none, we calculate the bounds by traversing the graph. // to get this, and if there is none, we calculate the bounds by traversing the graph.
FT_ClipBox clipBox; FT_ClipBox clipBox;
if (FT_Get_Color_Glyph_ClipBox(face, glyph, &clipBox) && true) { if (FT_Get_Color_Glyph_ClipBox(face, glyph, &clipBox)) {
FT_Pos left = qMin(clipBox.bottom_left.x, qMin(clipBox.bottom_right.x, qMin(clipBox.top_left.x, clipBox.top_right.x))); FT_Pos left = qMin(clipBox.bottom_left.x, qMin(clipBox.bottom_right.x, qMin(clipBox.top_left.x, clipBox.top_right.x)));
FT_Pos right = qMax(clipBox.bottom_left.x, qMax(clipBox.bottom_right.x, qMax(clipBox.top_left.x, clipBox.top_right.x))); FT_Pos right = qMax(clipBox.bottom_left.x, qMax(clipBox.bottom_right.x, qMax(clipBox.top_left.x, clipBox.top_right.x)));
@ -1822,10 +1641,17 @@ QFontEngineFT::Glyph *QFontEngineFT::loadColrv1Glyph(QGlyphSet *set,
QPoint(qCeil(right * scale), qCeil(bottom * scale))); QPoint(qCeil(right * scale), qCeil(bottom * scale)));
} else { } else {
// Do a pass over the graph to find the bounds // Do a pass over the graph to find the bounds
Colr1PaintInfo paintInfo; QColrPaintGraphRenderer boundingRectCalculator;
if (!traverseColr1(opaquePaint, &paintInfo)) boundingRectCalculator.beginCalculateBoundingBox();
return nullptr; QSet<QPair<FT_Byte *, FT_Bool> > loops;
designCoordinateBounds = paintInfo.boundingRect; if (traverseColr1(opaquePaint,
&loops,
QColor{},
nullptr,
0,
&boundingRectCalculator)) {
designCoordinateBounds = boundingRectCalculator.boundingRect().toAlignedRect();
}
} }
colrv1_bounds_cache_id = glyph; colrv1_bounds_cache_id = glyph;
@ -1852,12 +1678,8 @@ QFontEngineFT::Glyph *QFontEngineFT::loadColrv1Glyph(QGlyphSet *set,
if (FT_Palette_Data_Get(face, &paletteData)) if (FT_Palette_Data_Get(face, &paletteData))
return nullptr; return nullptr;
Colr1PaintInfo paintInfo; FT_Color *palette = nullptr;
paintInfo.foregroundColor = foregroundColor; FT_Error error = FT_Palette_Select(face, 0, &palette);
paintInfo.designCoordinateBoundingRect = designCoordinateBounds;
paintInfo.boundingRect = bounds;
FT_Error error = FT_Palette_Select(face, 0, &paintInfo.palette);
if (error) { if (error) {
qWarning("selecting palette for COLRv1 failed, err=%x face=%p, glyph=%d", qWarning("selecting palette for COLRv1 failed, err=%x face=%p, glyph=%d",
error, error,
@ -1865,37 +1687,28 @@ QFontEngineFT::Glyph *QFontEngineFT::loadColrv1Glyph(QGlyphSet *set,
glyph); glyph);
} }
if (paintInfo.palette == nullptr) if (palette == nullptr)
return nullptr; return nullptr;
paintInfo.paletteCount = paletteData.num_palette_entries; ushort paletteCount = paletteData.num_palette_entries;
destinationImage = QImage(bounds.size(), QImage::Format_ARGB32_Premultiplied); QColrPaintGraphRenderer paintGraphRenderer;
destinationImage.fill(Qt::transparent); paintGraphRenderer.setBoundingRect(bounds);
paintGraphRenderer.beginRender(fontDef.pixelSize / face->units_per_EM,
QPainter p; originalXform);
p.begin(&destinationImage);
p.setRenderHint(QPainter::Antialiasing);
p.setPen(Qt::NoPen);
p.setBrush(Qt::NoBrush);
// Move origin to top left of image
p.translate(-bounds.left(), -bounds.top());
// Scale to pixel size since shape is processed in font units
p.scale(fontDef.pixelSize / face->units_per_EM, fontDef.pixelSize / face->units_per_EM);
// Apply the original transform that we temporarily cleared
p.setWorldTransform(originalXform, true);
paintInfo.painter = &p;
// Render // Render
qCDebug(lcColrv1).noquote() << "================== Start rendering COLRv1 glyph" << glyph; QSet<QPair<FT_Byte *, FT_Bool> > loops;
if (!traverseColr1(opaquePaint, &paintInfo)) if (!traverseColr1(opaquePaint,
&loops,
foregroundColor,
palette,
paletteCount,
&paintGraphRenderer)) {
return nullptr; return nullptr;
}
p.end(); destinationImage = paintGraphRenderer.endRender();
} }
if (fetchMetricsOnly || !destinationImage.isNull()) { if (fetchMetricsOnly || !destinationImage.isNull()) {

View File

@ -38,6 +38,7 @@ QT_BEGIN_NAMESPACE
class QFontEngineFTRawFont; class QFontEngineFTRawFont;
class QFontconfigDatabase; class QFontconfigDatabase;
class QColrPaintGraphRenderer;
#if defined(FT_COLOR_H) && (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 21300 #if defined(FT_COLOR_H) && (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 21300
# define QFONTENGINE_FT_SUPPORT_COLRV1 # define QFONTENGINE_FT_SUPPORT_COLRV1
@ -332,20 +333,12 @@ private:
const QColor &color, const QColor &color,
bool fetchMetricsOnly) const; bool fetchMetricsOnly) const;
struct Colr1PaintInfo {
QTransform transform;
QPainterPath currentPath;
QPainter *painter = nullptr;
FT_Color *palette = nullptr;
ushort paletteCount = 0;
QRect boundingRect;
QRect designCoordinateBoundingRect;
QSet<QPair<FT_Byte *, FT_Bool> > loops;
QColor foregroundColor;
};
bool traverseColr1(FT_OpaquePaint paint, bool traverseColr1(FT_OpaquePaint paint,
Colr1PaintInfo *paintInfo) const; QSet<QPair<FT_Byte *, FT_Bool> > *loops,
QColor foregroundColor,
FT_Color *palette,
ushort paletteCount,
QColrPaintGraphRenderer *paintGraphRenderer) const;
mutable glyph_t colrv1_bounds_cache_id = 0; mutable glyph_t colrv1_bounds_cache_id = 0;
mutable QRect colrv1_bounds_cache; mutable QRect colrv1_bounds_cache;
#endif // QFONTENGINE_FT_SUPPORT_COLRV1 #endif // QFONTENGINE_FT_SUPPORT_COLRV1