Revision control

Copy as Markdown

// Copyright (C) 2023 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
// Qt-Security score:significant reason:default
#include "qsgrhiinternaltextnode_p.h"
#include <private/qquadpath_p.h>
#include <private/qsgcurvefillnode_p.h>
#include <private/qsgcurvestrokenode_p.h>
#include <private/qsgcurveprocessor_p.h>
#include <QtCore/qmath.h>
QT_BEGIN_NAMESPACE
QSGRhiInternalTextNode::QSGRhiInternalTextNode(QSGRenderContext *renderContext)
: QSGInternalTextNode(renderContext)
{
}
// Dash pattern arrays {dash, gap, ...} in units of pen width.
static constexpr qreal dashPattern[] = { 4, 2 };
static constexpr qreal dotPattern[] = { 1, 2 };
static constexpr qreal dashDotPattern[] = { 4, 2, 1, 2 };
static constexpr qreal dashDotDotPattern[] = { 4, 2, 1, 2, 1, 2 };
// Returns the dash pattern for a given underline style.
static QSpan<const qreal> dashPatternForStyle(QTextCharFormat::UnderlineStyle style)
{
switch (style) {
case QTextCharFormat::DashUnderline:
return dashPattern;
case QTextCharFormat::DotLine:
return dotPattern;
case QTextCharFormat::DashDotLine:
return dashDotPattern;
case QTextCharFormat::DashDotDotLine:
return dashDotDotPattern;
default:
return {};
}
}
// Builds a QQuadPath of dashed line segments along y = cy, from x0 to x1.
static QQuadPath buildDashedPath(qreal x0, qreal x1, qreal cy, QSpan<const qreal> pattern,
qreal penWidth)
{
Q_ASSERT(!pattern.isEmpty());
QQuadPath path;
qreal x = x0;
int idx = 0;
bool drawing = true; // pattern starts with a dash (drawn segment)
while (x < x1) {
// Round segment length to nearest pixel to avoid uneven dot sizes
const qreal rawLen = pattern[idx % pattern.size()] * penWidth;
const qreal segLen = qMax(qreal(1), qreal(qRound(rawLen)));
const qreal segEnd = qMin(x + segLen, x1);
if (drawing && segEnd > x) {
path.moveTo(QVector2D(qRound(x), cy));
path.lineTo(QVector2D(qRound(segEnd), cy));
}
x += segLen;
drawing = !drawing;
++idx;
}
return path;
}
static constexpr qreal goldenRatio = 1.6180339887498949;
// Builds a QQuadPath of a wavy line along y = cy, from x0 to x1.
// Uses quadratic Bezier curves, adapted from generateWavyPixmap() in QPainter.
static QQuadPath buildWavyPath(qreal x0, qreal x1, qreal cy, qreal penWidth)
{
const qreal radius = qMax(qreal(1), penWidth);
const qreal halfPeriod = qMax(qreal(2), radius * goldenRatio);
QQuadPath path;
path.moveTo(QVector2D(x0, cy));
qreal x = x0;
qreal dir = 1.0; // alternates between +1 and -1
while (x < x1) {
const qreal nextX = qMin(x + halfPeriod, x1);
const qreal fraction = (nextX - x) / halfPeriod;
const qreal peakY = cy + dir * radius * fraction;
const qreal ctrlX = (x + nextX) / 2.0;
path.quadTo(QVector2D(ctrlX, peakY), QVector2D(nextX, cy));
x = nextX;
dir = -dir;
}
return path;
}
// Helper: creates a QSGCurveStrokeNode from a QQuadPath and appends it.
static void strokePathToNode(QSGNode *parent, const QQuadPath &path, const QColor &color,
qreal penWidth)
{
QSGCurveStrokeNode *node = new QSGCurveStrokeNode;
node->setColor(color);
node->setStrokeWidth(penWidth);
QSGCurveProcessor::processStroke(
path, 2, penWidth, false, Qt::MiterJoin, Qt::FlatCap,
[&node](const std::array<QVector2D, 3> &s, const std::array<QVector2D, 3> &p,
const std::array<QVector2D, 3> &n, const std::array<float, 3> &ex,
QSGCurveStrokeNode::TriangleFlags /*f*/) {
node->appendTriangle(s, std::array<QVector2D, 2>{ p.at(0), p.at(2) }, n, ex);
});
node->cookGeometry();
parent->appendChildNode(node);
}
void QSGRhiInternalTextNode::addDecorationNode(const QRectF &rect,
const QColor &color,
QTextCharFormat::UnderlineStyle style,
RecycleBin *recycleBin)
{
if (rect.width() <= 0)
return;
if (recycleBin != nullptr)
recycleBin->stopReusing();
const qreal penWidth = rect.height();
const QPointF c = rect.center();
if (style == QTextCharFormat::WaveUnderline) {
QQuadPath path = buildWavyPath(rect.left(), rect.right(), c.y(), penWidth);
// Limit pen width so the wave doesn't get too fat
const qreal radius = qMax(qreal(1), penWidth);
const qreal maxPenWidth = 0.8 * radius;
strokePathToNode(this, path, color, qMin(penWidth, maxPenWidth));
return;
}
if (style != QTextCharFormat::SingleUnderline) {
// Dashed styles
QSpan<const qreal> pattern = dashPatternForStyle(style);
if (!pattern.isEmpty()) {
QQuadPath path = buildDashedPath(rect.left(), rect.right(), c.y(), pattern, penWidth);
strokePathToNode(this, path, color, penWidth);
return;
}
}
// SingleUnderline: solid line
QQuadPath path;
path.moveTo(QVector2D(rect.left(), c.y()));
path.lineTo(QVector2D(rect.right(), c.y()));
strokePathToNode(this, path, color, penWidth);
}
QT_END_NAMESPACE