| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB). |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt3D module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** 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. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qtext2dentity.h" |
| #include "qtext2dentity_p.h" |
| #include "qtext2dmaterial_p.h" |
| |
| #include <QtGui/qtextlayout.h> |
| #include <QtGui/qglyphrun.h> |
| #include <QtGui/private/qdistancefield_p.h> |
| #include <QtGui/private/qtextureglyphcache_p.h> |
| #include <QtGui/private/qfont_p.h> |
| #include <QtGui/private/qdistancefield_p.h> |
| |
| #include <Qt3DRender/qmaterial.h> |
| #include <Qt3DRender/qbuffer.h> |
| #include <Qt3DRender/qattribute.h> |
| #include <Qt3DRender/qgeometry.h> |
| #include <Qt3DRender/qgeometryrenderer.h> |
| |
| #include <Qt3DCore/private/qscene_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| namespace { |
| |
| inline Q_DECL_CONSTEXPR QRectF scaleRectF(const QRectF &rect, float scale) |
| { |
| return QRectF(rect.left() * scale, rect.top() * scale, rect.width() * scale, rect.height() * scale); |
| } |
| |
| } // anonymous |
| |
| namespace Qt3DExtras { |
| |
| /*! |
| * \qmltype Text2DEntity |
| * \instantiates Qt3DExtras::QText2DEntity |
| * \inqmlmodule Qt3D.Extras |
| * \brief Text2DEntity allows creation of a 2D text in 3D space. |
| * |
| * The Text2DEntity renders text as triangles in the XY plane. The geometry will be fitted |
| * in the rectangle of specified width and height. If the resulting geometry is wider than |
| * the specified width, the remainder will be rendered on the new line. |
| * |
| * The entity can be positionned in the scene by adding a transform component. |
| * |
| * Text2DEntity will create geometry based on the shape of the glyphs and a solid |
| * material using the specified color. |
| * |
| */ |
| |
| /*! |
| * \qmlproperty QString Text2DEntity::text |
| * |
| * Holds the text used for the mesh. |
| */ |
| |
| /*! |
| * \qmlproperty QFont Text2DEntity::font |
| * |
| * Holds the font of the text. |
| */ |
| |
| /*! |
| * \qmlproperty QColor Text2DEntity::color |
| * |
| * Holds the color of the text. |
| */ |
| |
| /*! |
| * \qmlproperty float Text2DEntity::width |
| * |
| * Holds the width of the text's bounding rectangle. |
| */ |
| |
| /*! |
| * \qmlproperty float Text2DEntity::height |
| * |
| * Holds the height of the text's bounding rectangle. |
| */ |
| |
| |
| /*! |
| * \class Qt3DExtras::QText2DEntity |
| * \inheaderfile Qt3DExtras/QText2DEntity |
| * \inmodule Qt3DExtras |
| * |
| * \brief QText2DEntity allows creation of a 2D text in 3D space. |
| * |
| * The QText2DEntity renders text as triangles in the XY plane. The geometry will be fitted |
| * in the rectangle of specified width and height. If the resulting geometry is wider than |
| * the specified width, the remainder will be rendered on the new line. |
| * |
| * The entity can be positionned in the scene by adding a transform component. |
| * |
| * QText2DEntity will create geometry based on the shape of the glyphs and a solid |
| * material using the specified color. |
| * |
| */ |
| |
| QHash<Qt3DCore::QScene *, QText2DEntityPrivate::CacheEntry> QText2DEntityPrivate::m_glyphCacheInstances; |
| |
| QText2DEntityPrivate::QText2DEntityPrivate() |
| : m_glyphCache(nullptr) |
| , m_font(QLatin1String("Times"), 10) |
| , m_scaledFont(QLatin1String("Times"), 10) |
| , m_color(QColor(255, 255, 255, 255)) |
| , m_width(0.0f) |
| , m_height(0.0f) |
| { |
| } |
| |
| QText2DEntityPrivate::~QText2DEntityPrivate() |
| { |
| } |
| |
| void QText2DEntityPrivate::setScene(Qt3DCore::QScene *scene) |
| { |
| if (scene == m_scene) |
| return; |
| |
| // Unref old glyph cache if it exists |
| if (m_scene != nullptr) { |
| // Ensure we don't keep reference to glyphs |
| // if we are changing the cache |
| if (m_glyphCache != nullptr) |
| clearCurrentGlyphRuns(); |
| |
| m_glyphCache = nullptr; |
| |
| QText2DEntityPrivate::CacheEntry &entry = QText2DEntityPrivate::m_glyphCacheInstances[m_scene]; |
| --entry.count; |
| if (entry.count == 0 && entry.glyphCache != nullptr) { |
| |
| delete entry.glyphCache; |
| entry.glyphCache = nullptr; |
| } |
| } |
| |
| QEntityPrivate::setScene(scene); |
| |
| // Ref new glyph cache is scene is valid |
| if (scene != nullptr) { |
| QText2DEntityPrivate::CacheEntry &entry = QText2DEntityPrivate::m_glyphCacheInstances[scene]; |
| if (entry.glyphCache == nullptr) { |
| entry.glyphCache = new QDistanceFieldGlyphCache(); |
| entry.glyphCache->setRootNode(scene->rootNode()); |
| } |
| m_glyphCache = entry.glyphCache; |
| ++entry.count; |
| // Update to populate glyphCache if needed |
| update(); |
| } |
| } |
| |
| QText2DEntity::QText2DEntity(QNode *parent) |
| : Qt3DCore::QEntity(*new QText2DEntityPrivate(), parent) |
| { |
| } |
| |
| /*! \internal */ |
| QText2DEntity::~QText2DEntity() |
| { |
| } |
| |
| float QText2DEntityPrivate::computeActualScale() const |
| { |
| // scale font based on fontScale property and given QFont |
| float scale = 1.0f; |
| if (m_font.pointSizeF() > 0) |
| scale *= m_font.pointSizeF() / m_scaledFont.pointSizeF(); |
| return scale; |
| } |
| |
| struct RenderData { |
| int vertexCount = 0; |
| QVector<float> vertex; |
| QVector<quint16> index; |
| }; |
| |
| void QText2DEntityPrivate::setCurrentGlyphRuns(const QVector<QGlyphRun> &runs) |
| { |
| // For each distinct texture, we need a separate DistanceFieldTextRenderer, |
| // for which we need vertex and index data |
| QHash<Qt3DRender::QAbstractTexture*, RenderData> renderData; |
| const float scale = computeActualScale(); |
| |
| // process glyph runs |
| for (const QGlyphRun &run : runs) { |
| const QVector<quint32> glyphs = run.glyphIndexes(); |
| const QVector<QPointF> pos = run.positions(); |
| |
| Q_ASSERT(glyphs.size() == pos.size()); |
| |
| const bool doubleGlyphResolution = m_glyphCache->doubleGlyphResolution(run.rawFont()); |
| |
| // faithfully copied from QSGDistanceFieldGlyphNode::updateGeometry() |
| const float pixelSize = run.rawFont().pixelSize(); |
| const float fontScale = pixelSize / QT_DISTANCEFIELD_BASEFONTSIZE(doubleGlyphResolution); |
| const float margin = QT_DISTANCEFIELD_RADIUS(doubleGlyphResolution) / QT_DISTANCEFIELD_SCALE(doubleGlyphResolution) * fontScale; |
| |
| for (int i = 0; i < glyphs.size(); i++) { |
| const QDistanceFieldGlyphCache::Glyph &dfield = m_glyphCache->refGlyph(run.rawFont(), glyphs[i]); |
| |
| if (!dfield.texture) |
| continue; |
| |
| RenderData &data = renderData[dfield.texture]; |
| |
| // faithfully copied from QSGDistanceFieldGlyphNode::updateGeometry() |
| QRectF metrics = scaleRectF(dfield.glyphPathBoundingRect, fontScale); |
| metrics.adjust(-margin, margin, margin, 3*margin); |
| |
| const float top = 0.0f; |
| const float left = 0.0f; |
| const float right = m_width; |
| const float bottom = m_height; |
| |
| float x1 = left + scale * (pos[i].x() + metrics.left()); |
| float y2 = bottom - scale * (pos[i].y() - metrics.top()); |
| float x2 = x1 + scale * metrics.width(); |
| float y1 = y2 - scale * metrics.height(); |
| |
| // only draw glyphs that are at least partly visible |
| if (y2 < top || x1 > right) |
| continue; |
| |
| QRectF texCoords = dfield.texCoords; |
| |
| // if a glyph is only partly visible within the given rectangle, |
| // cut it in half and adjust tex coords |
| if (y1 < top) { |
| const float insideRatio = (top - y2) / (y1 - y2); |
| y1 = top; |
| texCoords.setHeight(texCoords.height() * insideRatio); |
| } |
| |
| // do the same thing horizontally |
| if (x2 > right) { |
| const float insideRatio = (right - x1) / (x2 - x1); |
| x2 = right; |
| texCoords.setWidth(texCoords.width() * insideRatio); |
| } |
| |
| data.vertex << x1 << y1 << i << texCoords.left() << texCoords.bottom(); |
| data.vertex << x1 << y2 << i << texCoords.left() << texCoords.top(); |
| data.vertex << x2 << y1 << i << texCoords.right() << texCoords.bottom(); |
| data.vertex << x2 << y2 << i << texCoords.right() << texCoords.top(); |
| |
| data.index << data.vertexCount << data.vertexCount+3 << data.vertexCount+1; |
| data.index << data.vertexCount << data.vertexCount+2 << data.vertexCount+3; |
| |
| data.vertexCount += 4; |
| } |
| } |
| |
| // de-ref all glyphs for previous QGlyphRuns |
| for (int i = 0; i < m_currentGlyphRuns.size(); i++) |
| m_glyphCache->derefGlyphs(m_currentGlyphRuns[i]); |
| m_currentGlyphRuns = runs; |
| |
| // make sure we have the correct number of DistanceFieldTextRenderers |
| // TODO: we might keep one renderer at all times, so we won't delete and |
| // re-allocate one every time the text changes from an empty to a non-empty string |
| // and vice-versa |
| while (m_renderers.size() > renderData.size()) |
| delete m_renderers.takeLast(); |
| |
| while (m_renderers.size() < renderData.size()) { |
| DistanceFieldTextRenderer *renderer = new DistanceFieldTextRenderer(q_func()); |
| renderer->setColor(m_color); |
| m_renderers << renderer; |
| } |
| |
| Q_ASSERT(m_renderers.size() == renderData.size()); |
| |
| // assign vertex data for all textures to the renderers |
| int rendererIdx = 0; |
| for (auto it = renderData.begin(); it != renderData.end(); ++it) { |
| m_renderers[rendererIdx++]->setGlyphData(it.key(), it.value().vertex, it.value().index); |
| } |
| } |
| |
| void QText2DEntityPrivate::clearCurrentGlyphRuns() |
| { |
| for (int i = 0; i < m_currentGlyphRuns.size(); i++) |
| m_glyphCache->derefGlyphs(m_currentGlyphRuns[i]); |
| m_currentGlyphRuns.clear(); |
| } |
| |
| void QText2DEntityPrivate::update() |
| { |
| if (m_glyphCache == nullptr) |
| return; |
| |
| QVector<QGlyphRun> glyphRuns; |
| |
| // collect all GlyphRuns generated by the QTextLayout |
| if ((m_width > 0.0f || m_height > 0.0f) && !m_text.isEmpty()) { |
| QTextLayout layout(m_text, m_scaledFont); |
| const float lineWidth = m_width / computeActualScale(); |
| float height = 0; |
| layout.beginLayout(); |
| |
| while (true) { |
| QTextLine line = layout.createLine(); |
| if (!line.isValid()) |
| break; |
| |
| // position current line |
| line.setLineWidth(lineWidth); |
| line.setPosition(QPointF(0, height)); |
| height += line.height(); |
| |
| // add glyph runs created by line |
| const QList<QGlyphRun> runs = line.glyphRuns(); |
| for (const QGlyphRun &run : runs) |
| glyphRuns << run; |
| } |
| |
| layout.endLayout(); |
| } |
| |
| setCurrentGlyphRuns(glyphRuns); |
| } |
| |
| /*! |
| \property QText2DEntity::font |
| |
| Holds the font for the text item that is displayed |
| in the Qt Quick scene. |
| */ |
| QFont QText2DEntity::font() const |
| { |
| Q_D(const QText2DEntity); |
| return d->m_font; |
| } |
| |
| void QText2DEntity::setFont(const QFont &font) |
| { |
| Q_D(QText2DEntity); |
| if (d->m_font != font) { |
| // ignore the point size of the font, just make it a default value. |
| // still we want to make sure that font() returns the same value |
| // that was passed to setFont(), so we store it nevertheless |
| d->m_font = font; |
| d->m_scaledFont = font; |
| d->m_scaledFont.setPointSize(10); |
| |
| emit fontChanged(font); |
| |
| if (!d->m_text.isEmpty()) |
| d->update(); |
| } |
| } |
| |
| /*! |
| \property QText2DEntity::color |
| |
| Holds the color for the text item that is displayed in the Qt |
| Quick scene. |
| */ |
| QColor QText2DEntity::color() const |
| { |
| Q_D(const QText2DEntity); |
| return d->m_color; |
| } |
| |
| void QText2DEntity::setColor(const QColor &color) |
| { |
| Q_D(QText2DEntity); |
| if (d->m_color != color) { |
| d->m_color = color; |
| |
| emit colorChanged(color); |
| |
| for (DistanceFieldTextRenderer *renderer : qAsConst(d->m_renderers)) |
| renderer->setColor(color); |
| } |
| } |
| |
| /*! |
| \property QText2DEntity::text |
| |
| Holds the text that is displayed in the Qt Quick scene. |
| */ |
| QString QText2DEntity::text() const |
| { |
| Q_D(const QText2DEntity); |
| return d->m_text; |
| } |
| |
| void QText2DEntity::setText(const QString &text) |
| { |
| Q_D(QText2DEntity); |
| if (d->m_text != text) { |
| d->m_text = text; |
| emit textChanged(text); |
| |
| d->update(); |
| } |
| } |
| |
| /*! |
| \property QText2DEntity::width |
| |
| Returns the width of the text item that is displayed in the |
| Qt Quick scene. |
| */ |
| float QText2DEntity::width() const |
| { |
| Q_D(const QText2DEntity); |
| return d->m_width; |
| } |
| |
| /*! |
| \property QText2DEntity::height |
| |
| Returns the height of the text item that is displayed in the |
| Qt Quick scene. |
| */ |
| float QText2DEntity::height() const |
| { |
| Q_D(const QText2DEntity); |
| return d->m_height; |
| } |
| |
| void QText2DEntity::setWidth(float width) |
| { |
| Q_D(QText2DEntity); |
| if (width != d->m_width) { |
| d->m_width = width; |
| emit widthChanged(width); |
| d->update(); |
| } |
| } |
| |
| void QText2DEntity::setHeight(float height) |
| { |
| Q_D(QText2DEntity); |
| if (height != d->m_height) { |
| d->m_height = height; |
| emit heightChanged(height); |
| d->update(); |
| } |
| } |
| |
| } // namespace Qt3DExtras |
| |
| QT_END_NAMESPACE |