Introduce PathText path element

For text rendering in Qt Quick, we currently have the limitation
that when rendering text at such a large size that the distance
fields start showing artifacts, the only option is to use
NativeRendering, which will look nice, but which will use a lot
of texture memory for the glyph cache, since it will actually
cache the glyphs at the requested size.

A suggested approach would be to fall back to using triangulated
paths when the font gets large enough, but the work on this was
never completed.

It turns out that we can get this now, basically for free, since
we already support rendering arbitrary QPainterPaths using
Qt Quick Shapes. The only thing missing is the ability to add
the path of a given text to the shape. This patch fills in that
gap.

Note that this is currently not supported by nvidia renderer.

[ChangeLog][QtQuick] Added PathText path element which can be
used together with Qt Quick Shapes to get text rendering that
does not cache glyphs in a texture, but triangulates the
outlines of the glyphs instead.

Change-Id: I436e1476b129b324cf7a54f89a1b18e0579e8185
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
This commit is contained in:
Eskil Abrahamsen Blomfeldt 2020-01-15 11:53:35 +01:00
parent fd272b60c3
commit 1ee5fed75f
7 changed files with 404 additions and 1 deletions

View File

@ -0,0 +1,71 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQuick module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.15
import QtQuick.Shapes 1.0
Rectangle {
color: "lightGray"
Shape {
anchors.centerIn: parent
width: 200
height: 100
ShapePath {
id: capTest
strokeColor: "black"
strokeWidth: 1
fillColor: "black"
PathText { x: 0; y: 100; font.family: "Arial"; font.pixelSize: 150; text: "Qt!" }
}
}
}

View File

@ -130,6 +130,10 @@ Rectangle {
name: "Tiger"
shapeUrl: "item17.qml"
}
ListElement {
name: "Text"
shapeUrl: "item18.qml"
}
}
property int gridSpacing: 10

View File

@ -24,7 +24,8 @@ OTHER_FILES += content/main.qml \
content/item13.qml \
content/item14.qml \
content/item15.qml \
content/item17.qml
content/item17.qml \
content/item18.qml
target.path = $$[QT_INSTALL_EXAMPLES]/quick/shapes
INSTALLS += target

View File

@ -28,5 +28,6 @@
<file alias="item14.qml">content/item14.qml</file>
<file alias="item15.qml">content/item15.qml</file>
<file alias="item17.qml">content/item17.qml</file>
<file alias="item18.qml">content/item18.qml</file>
</qresource>
</RCC>

View File

@ -301,6 +301,8 @@ void QQuickPath::pathElements_append(QQmlListProperty<QQuickPathElement> *proper
QQuickCurve *curve = qobject_cast<QQuickCurve *>(pathElement);
if (curve)
d->_pathCurves.append(curve);
else if (QQuickPathText *text = qobject_cast<QQuickPathText *>(pathElement))
d->_pathTexts.append(text);
else {
QQuickPathAttribute *attribute = qobject_cast<QQuickPathAttribute *>(pathElement);
if (attribute && !d->_attributes.contains(attribute->name()))
@ -329,6 +331,7 @@ void QQuickPath::pathElements_clear(QQmlListProperty<QQuickPathElement> *propert
d->_pathElements.clear();
d->_pathCurves.clear();
d->_pointCache.clear();
d->_pathTexts.clear();
}
void QQuickPath::interpolate(int idx, const QString &name, qreal value)
@ -478,6 +481,8 @@ QPainterPath QQuickPath::createPath(const QPointF &startPoint, const QPointF &en
point.values[percentString] = percent->value();
interpolate(attributePoints, attributePoints.count() - 1, percentString, percent->value());
usesPercent = true;
} else if (QQuickPathText *text = qobject_cast<QQuickPathText *>(pathElement)) {
text->addToPath(path);
}
}
@ -547,6 +552,9 @@ QPainterPath QQuickPath::createShapePath(const QPointF &startPoint, const QPoint
++index;
}
for (QQuickPathText *text : qAsConst(d->_pathTexts))
text->addToPath(path);
if (closed) {
QPointF end = path.currentPosition();
*closed = startX == end.x() && startY == end.y();
@ -593,6 +601,8 @@ void QQuickPath::gatherAttributes()
for (QQuickPathElement *pathElement : qAsConst(d->_pathElements)) {
if (QQuickCurve *curve = qobject_cast<QQuickCurve *>(pathElement))
d->_pathCurves.append(curve);
else if (QQuickPathText *text = qobject_cast<QQuickPathText *>(pathElement))
d->_pathTexts.append(text);
else if (QQuickPathAttribute *attribute = qobject_cast<QQuickPathAttribute *>(pathElement))
attributes.insert(attribute->name());
}
@ -2619,6 +2629,219 @@ void QQuickPathMultiline::addToPath(QPainterPath &path, const QQuickPathData &)
}
}
/*!
\qmltype PathText
\instantiates QQuickPathText
\inqmlmodule QtQuick
\ingroup qtquick-animation-paths
\brief Defines a string in a specified font.
\since QtQuick 2.15
This element defines the shape of a specified string in a specified font. The text's
baseline will be translated to the x and y coordinates, and the outlines from the font
will be added to the path accordingly.
\qml
PathText {
x: 0
y: font.pixelSize
font.family: "Arial"
font.pixelSize: 100
text: "Foobar"
}
\endqml
\sa Path, QPainterPath::setFillRule, PathPolyline, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove
*/
/*!
\qmlproperty real QtQuick::PathText::x
The horizontal position of the PathText's baseline.
*/
/*!
\qmlproperty real QtQuick::PathText::y
The vertical position of the PathText's baseline.
\note This property refers to the position of the baseline of the text, not the top of its bounding box. This may
cause some confusion, e.g. when using the PathText with Qt Quick Shapes. See \l FontMetrics for information on how to
get the ascent of a font, which can be used to translate the text into the expected position.
*/
/*!
\qmlproperty string QtQuick::PathText::text
The text for which this PathText should contain the outlines.
*/
/*!
\qmlproperty string QtQuick::PathText::font.family
Sets the family name of the font.
The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]".
If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen.
If the family isn't available a family will be set using the font matching algorithm.
*/
/*!
\qmlproperty string QtQuick::PathText::font.styleName
Sets the style name of the font.
The style name is case insensitive. If set, the font will be matched against style name instead
of the font properties \l font.weight, \l font.bold and \l font.italic.
*/
/*!
\qmlproperty bool QtQuick::PathText::font.bold
Sets whether the font weight is bold.
*/
/*!
\qmlproperty enumeration QtQuick::PathText::font.weight
Sets the font's weight.
The weight can be one of:
\list
\li Font.Thin
\li Font.Light
\li Font.ExtraLight
\li Font.Normal - the default
\li Font.Medium
\li Font.DemiBold
\li Font.Bold
\li Font.ExtraBold
\li Font.Black
\endlist
\qml
PathText { text: "Hello"; font.weight: Font.DemiBold }
\endqml
*/
/*!
\qmlproperty bool QtQuick::PathText::font.italic
Sets whether the font has an italic style.
*/
/*!
\qmlproperty bool QtQuick::PathText::font.underline
Sets whether the text is underlined.
*/
/*!
\qmlproperty bool QtQuick::PathText::font.strikeout
Sets whether the font has a strikeout style.
*/
/*!
\qmlproperty real QtQuick::PathText::font.pointSize
Sets the font size in points. The point size must be greater than zero.
*/
/*!
\qmlproperty int QtQuick::PathText::font.pixelSize
Sets the font size in pixels.
Using this function makes the font device dependent.
Use \c pointSize to set the size of the font in a device independent manner.
*/
/*!
\qmlproperty real QtQuick::PathText::font.letterSpacing
Sets the letter spacing for the font.
Letter spacing changes the default spacing between individual letters in the font.
A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing.
*/
/*!
\qmlproperty real QtQuick::PathText::font.wordSpacing
Sets the word spacing for the font.
Word spacing changes the default spacing between individual words.
A positive value increases the word spacing by a corresponding amount of pixels,
while a negative value decreases the inter-word spacing accordingly.
*/
/*!
\qmlproperty enumeration QtQuick::PathText::font.capitalization
Sets the capitalization for the text.
\list
\li Font.MixedCase - This is the normal text rendering option where no capitalization change is applied.
\li Font.AllUppercase - This alters the text to be rendered in all uppercase type.
\li Font.AllLowercase - This alters the text to be rendered in all lowercase type.
\li Font.SmallCaps - This alters the text to be rendered in small-caps type.
\li Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character.
\endlist
\qml
PathText { text: "Hello"; font.capitalization: Font.AllLowercase }
\endqml
*/
/*!
\qmlproperty bool QtQuick::PathText::font.kerning
Enables or disables the kerning OpenType feature when shaping the text. Disabling this may
improve performance when creating or changing the text, at the expense of some cosmetic
features. The default value is true.
\qml
PathText { text: "OATS FLAVOUR WAY"; font.kerning: false }
\endqml
*/
/*!
\qmlproperty bool QtQuick::PathText::font.preferShaping
Sometimes, a font will apply complex rules to a set of characters in order to
display them correctly. In some writing systems, such as Brahmic scripts, this is
required in order for the text to be legible, but in e.g. Latin script, it is merely
a cosmetic feature. Setting the \c preferShaping property to false will disable all
such features when they are not required, which will improve performance in most cases.
The default value is true.
\qml
PathText { text: "Some text"; font.preferShaping: false }
\endqml
*/
void QQuickPathText::updatePath() const
{
if (!_path.isEmpty())
return;
_path.addText(_x, _y, _font, _text);
// Account for distance from baseline to top, since addText() takes baseline position
QRectF brect = _path.boundingRect();
_path.translate(0.0, -brect.y());
}
void QQuickPathText::addToPath(QPainterPath &path)
{
if (_text.isEmpty())
return;
updatePath();
path.addPath(_path);
}
QT_END_NAMESPACE
#include "moc_qquickpath_p.cpp"

View File

@ -63,6 +63,7 @@ QT_REQUIRE_CONFIG(quick_path);
#include <QtCore/QObject>
#include <QtGui/QPainterPath>
#include <QtGui/QFont>
QT_BEGIN_NAMESPACE
@ -598,6 +599,106 @@ public:
static QPointF sequentialPointAt(const QPainterPath &path, const qreal &pathLength, const QList<AttributePoint> &attributePoints, QQuickCachedBezier &prevBez, qreal p, qreal *angle = nullptr);
};
class Q_QUICK_PRIVATE_EXPORT QQuickPathText : public QQuickPathElement
{
Q_OBJECT
Q_PROPERTY(qreal x READ x WRITE setX NOTIFY xChanged)
Q_PROPERTY(qreal y READ y WRITE setY NOTIFY yChanged)
Q_PROPERTY(qreal width READ width NOTIFY changed)
Q_PROPERTY(qreal height READ height NOTIFY changed)
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
QML_NAMED_ELEMENT(PathText)
QML_ADDED_IN_MINOR_VERSION(15)
public:
QQuickPathText(QObject *parent=nullptr) : QQuickPathElement(parent)
{
connect(this, &QQuickPathText::xChanged, this, &QQuickPathElement::changed);
connect(this, &QQuickPathText::yChanged, this, &QQuickPathElement::changed);
connect(this, &QQuickPathText::textChanged, this, &QQuickPathElement::changed);
connect(this, &QQuickPathText::fontChanged, this, &QQuickPathElement::changed);
connect(this, &QQuickPathElement::changed, this, &QQuickPathText::invalidate);
}
void addToPath(QPainterPath &path);
qreal x() const { return _x; }
qreal y() const { return _y; }
QString text() const { return _text; }
QFont font() const { return _font; }
void setX(qreal x)
{
if (qFuzzyCompare(_x, x))
return;
_x = x;
Q_EMIT xChanged();
}
void setY(qreal y)
{
if (qFuzzyCompare(_y, y))
return;
_y = y;
Q_EMIT yChanged();
}
void setText(const QString &text)
{
if (text == _text)
return;
_text = text;
Q_EMIT textChanged();
}
void setFont(const QFont &font)
{
if (font == _font)
return;
_font = font;
Q_EMIT fontChanged();
}
qreal width() const
{
updatePath();
return _path.boundingRect().width();
}
qreal height() const
{
updatePath();
return _path.boundingRect().height();
}
Q_SIGNALS:
void xChanged();
void yChanged();
void textChanged();
void fontChanged();
private Q_SLOTS:
void invalidate()
{
_path.clear();
}
private:
void updatePath() const;
QString _text;
qreal _x = qreal(0.0);
qreal _y = qreal(0.0);
QFont _font;
mutable QPainterPath _path;
};
QT_END_NAMESPACE
QML_DECLARE_TYPE(QQuickPathElement)
@ -614,5 +715,6 @@ QML_DECLARE_TYPE(QQuickPathSvg)
QML_DECLARE_TYPE(QQuickPathPercent)
QML_DECLARE_TYPE(QQuickPathPolyline)
QML_DECLARE_TYPE(QQuickPath)
QML_DECLARE_TYPE(QQuickPathText)
#endif // QQUICKPATH_H

View File

@ -80,6 +80,7 @@ public:
QList<QQuickPath::AttributePoint> _attributePoints;
QStringList _attributes;
QList<QQuickCurve*> _pathCurves;
QList<QQuickPathText*> _pathTexts;
mutable QQuickCachedBezier prevBez;
QQmlNullableValue<qreal> startX;
QQmlNullableValue<qreal> startY;