/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include 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 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 vertex; QVector index; }; void QText2DEntityPrivate::setCurrentGlyphRuns(const QVector &runs) { // For each distinct texture, we need a separate DistanceFieldTextRenderer, // for which we need vertex and index data QHash renderData; const float scale = computeActualScale(); // process glyph runs for (const QGlyphRun &run : runs) { const QVector glyphs = run.glyphIndexes(); const QVector 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(); renderer->setColor(m_color); renderer->setParent(q_func()); 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 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 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 #include "moc_qtext2dentity.cpp"