diff --git a/examples/quick/shapes/content/item18.qml b/examples/quick/shapes/content/item18.qml new file mode 100644 index 0000000000..3774d19bc5 --- /dev/null +++ b/examples/quick/shapes/content/item18.qml @@ -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!" } + } + } +} diff --git a/examples/quick/shapes/content/shapegallery.qml b/examples/quick/shapes/content/shapegallery.qml index 86445e25c2..e5a7c51483 100644 --- a/examples/quick/shapes/content/shapegallery.qml +++ b/examples/quick/shapes/content/shapegallery.qml @@ -130,6 +130,10 @@ Rectangle { name: "Tiger" shapeUrl: "item17.qml" } + ListElement { + name: "Text" + shapeUrl: "item18.qml" + } } property int gridSpacing: 10 diff --git a/examples/quick/shapes/shapes.pro b/examples/quick/shapes/shapes.pro index ff6fa422fb..f99d941804 100644 --- a/examples/quick/shapes/shapes.pro +++ b/examples/quick/shapes/shapes.pro @@ -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 diff --git a/examples/quick/shapes/shapes.qrc b/examples/quick/shapes/shapes.qrc index e03c0e8a0a..6de463dd33 100644 --- a/examples/quick/shapes/shapes.qrc +++ b/examples/quick/shapes/shapes.qrc @@ -28,5 +28,6 @@ content/item14.qml content/item15.qml content/item17.qml + content/item18.qml diff --git a/src/quick/util/qquickpath.cpp b/src/quick/util/qquickpath.cpp index c855acc185..375d0265e8 100644 --- a/src/quick/util/qquickpath.cpp +++ b/src/quick/util/qquickpath.cpp @@ -301,6 +301,8 @@ void QQuickPath::pathElements_append(QQmlListProperty *proper QQuickCurve *curve = qobject_cast(pathElement); if (curve) d->_pathCurves.append(curve); + else if (QQuickPathText *text = qobject_cast(pathElement)) + d->_pathTexts.append(text); else { QQuickPathAttribute *attribute = qobject_cast(pathElement); if (attribute && !d->_attributes.contains(attribute->name())) @@ -329,6 +331,7 @@ void QQuickPath::pathElements_clear(QQmlListProperty *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(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(pathElement)) d->_pathCurves.append(curve); + else if (QQuickPathText *text = qobject_cast(pathElement)) + d->_pathTexts.append(text); else if (QQuickPathAttribute *attribute = qobject_cast(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" diff --git a/src/quick/util/qquickpath_p.h b/src/quick/util/qquickpath_p.h index ca0495a90d..159c46d13c 100644 --- a/src/quick/util/qquickpath_p.h +++ b/src/quick/util/qquickpath_p.h @@ -63,6 +63,7 @@ QT_REQUIRE_CONFIG(quick_path); #include #include +#include QT_BEGIN_NAMESPACE @@ -598,6 +599,106 @@ public: static QPointF sequentialPointAt(const QPainterPath &path, const qreal &pathLength, const QList &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 diff --git a/src/quick/util/qquickpath_p_p.h b/src/quick/util/qquickpath_p_p.h index e26001ec77..5505b876c1 100644 --- a/src/quick/util/qquickpath_p_p.h +++ b/src/quick/util/qquickpath_p_p.h @@ -80,6 +80,7 @@ public: QList _attributePoints; QStringList _attributes; QList _pathCurves; + QList _pathTexts; mutable QQuickCachedBezier prevBez; QQmlNullableValue startX; QQmlNullableValue startY;