blob: 0fa680a244c02dfe72cef1db6bac66ca981c3072 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 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: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 "qsgdistancefieldglyphnode_p.h"
#include "qsgdistancefieldglyphnode_p_p.h"
#include <QtQuick/private/qsgcontext_p.h>
QT_BEGIN_NAMESPACE
QSGDistanceFieldGlyphNode::QSGDistanceFieldGlyphNode(QSGRenderContext *context)
: m_glyphNodeType(RootGlyphNode)
, m_context(context)
, m_material(nullptr)
, m_glyph_cache(nullptr)
, m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 0)
, m_style(QQuickText::Normal)
, m_antialiasingMode(GrayAntialiasing)
, m_texture(nullptr)
, m_dirtyGeometry(false)
, m_dirtyMaterial(false)
{
m_geometry.setDrawingMode(GL_TRIANGLES);
setGeometry(&m_geometry);
setFlag(UsePreprocess);
#ifdef QSG_RUNTIME_DESCRIPTION
qsgnode_set_description(this, QLatin1String("glyphs"));
#endif
}
QSGDistanceFieldGlyphNode::~QSGDistanceFieldGlyphNode()
{
delete m_material;
if (m_glyphNodeType == SubGlyphNode)
return;
if (m_glyph_cache) {
m_glyph_cache->release(m_glyphs.glyphIndexes());
m_glyph_cache->unregisterGlyphNode(this);
m_glyph_cache->unregisterOwnerElement(ownerElement());
}
}
void QSGDistanceFieldGlyphNode::setColor(const QColor &color)
{
m_color = color;
if (m_material != nullptr) {
m_material->setColor(color);
markDirty(DirtyMaterial);
} else {
m_dirtyMaterial = true;
}
}
void QSGDistanceFieldGlyphNode::setPreferredAntialiasingMode(AntialiasingMode mode)
{
if (mode == m_antialiasingMode)
return;
m_antialiasingMode = mode;
m_dirtyMaterial = true;
}
void QSGDistanceFieldGlyphNode::setGlyphs(const QPointF &position, const QGlyphRun &glyphs)
{
QRawFont font = glyphs.rawFont();
m_originalPosition = position;
m_position = QPointF(position.x(), position.y() - font.ascent());
m_glyphs = glyphs;
m_dirtyGeometry = true;
m_dirtyMaterial = true;
QSGDistanceFieldGlyphCache *oldCache = m_glyph_cache;
m_glyph_cache = m_context->distanceFieldGlyphCache(m_glyphs.rawFont());
if (m_glyphNodeType == SubGlyphNode)
return;
if (m_glyph_cache != oldCache) {
Q_ASSERT(ownerElement() != nullptr);
if (oldCache) {
oldCache->unregisterGlyphNode(this);
oldCache->unregisterOwnerElement(ownerElement());
}
m_glyph_cache->registerGlyphNode(this);
m_glyph_cache->registerOwnerElement(ownerElement());
}
m_glyph_cache->populate(glyphs.glyphIndexes());
const QVector<quint32> glyphIndexes = m_glyphs.glyphIndexes();
for (int i = 0; i < glyphIndexes.count(); ++i)
m_allGlyphIndexesLookup.insert(glyphIndexes.at(i));
}
void QSGDistanceFieldGlyphNode::setStyle(QQuickText::TextStyle style)
{
if (m_style == style)
return;
m_style = style;
m_dirtyMaterial = true;
}
void QSGDistanceFieldGlyphNode::setStyleColor(const QColor &color)
{
if (m_styleColor == color)
return;
m_styleColor = color;
m_dirtyMaterial = true;
}
void QSGDistanceFieldGlyphNode::update()
{
if (m_dirtyMaterial)
updateMaterial();
}
void QSGDistanceFieldGlyphNode::preprocess()
{
Q_ASSERT(m_glyph_cache);
m_glyph_cache->processPendingGlyphs();
m_glyph_cache->update();
if (m_dirtyGeometry)
updateGeometry();
}
void QSGDistanceFieldGlyphNode::invalidateGlyphs(const QVector<quint32> &glyphs)
{
if (m_dirtyGeometry)
return;
for (int i = 0; i < glyphs.count(); ++i) {
if (m_allGlyphIndexesLookup.contains(glyphs.at(i))) {
m_dirtyGeometry = true;
return;
}
}
}
void QSGDistanceFieldGlyphNode::updateGeometry()
{
Q_ASSERT(m_glyph_cache);
// Remove previously created sub glyph nodes
// We assume all the children are sub glyph nodes
QSGNode *subnode = firstChild();
QSGNode *nextNode = nullptr;
while (subnode) {
nextNode = subnode->nextSibling();
delete subnode;
subnode = nextNode;
}
QSGGeometry *g = geometry();
Q_ASSERT(g->indexType() == GL_UNSIGNED_SHORT);
QHash<const QSGDistanceFieldGlyphCache::Texture *, GlyphInfo> glyphsInOtherTextures;
const QVector<quint32> indexes = m_glyphs.glyphIndexes();
const QVector<QPointF> positions = m_glyphs.positions();
qreal fontPixelSize = m_glyphs.rawFont().pixelSize();
// The template parameters here are assuming that most strings are short, 64
// characters or less.
QVarLengthArray<QSGGeometry::TexturedPoint2D, 256> vp;
vp.reserve(indexes.size() * 4);
QVarLengthArray<ushort, 384> ip;
ip.reserve(indexes.size() * 6);
qreal maxTexMargin = m_glyph_cache->distanceFieldRadius();
qreal fontScale = m_glyph_cache->fontScale(fontPixelSize);
qreal margin = 2;
qreal texMargin = margin / fontScale;
if (texMargin > maxTexMargin) {
texMargin = maxTexMargin;
margin = maxTexMargin * fontScale;
}
for (int i = 0; i < indexes.size(); ++i) {
const int glyphIndex = indexes.at(i);
QSGDistanceFieldGlyphCache::TexCoord c = m_glyph_cache->glyphTexCoord(glyphIndex);
if (c.isNull())
continue;
const QPointF position = positions.at(i);
const QSGDistanceFieldGlyphCache::Texture *texture = m_glyph_cache->glyphTexture(glyphIndex);
if ((!texture->rhiBased && texture->textureId && !m_texture)
|| (texture->rhiBased && texture->texture && !m_texture))
{
m_texture = texture;
}
// As we use UNSIGNED_SHORT indexing in the geometry, we overload the
// "glyphsInOtherTextures" concept as overflow for if there are more
// than 65535 vertices to render which would otherwise exceed the
// maximum index size. (leave 0xFFFF unused in order not to clash with
// primitive restart) This will cause sub-nodes to be recursively
// created to handle any number of glyphs.
if (m_texture != texture || vp.size() >= 65535) {
if (texture->textureId) {
GlyphInfo &glyphInfo = glyphsInOtherTextures[texture];
glyphInfo.indexes.append(glyphIndex);
glyphInfo.positions.append(position);
}
continue;
}
QSGDistanceFieldGlyphCache::Metrics metrics = m_glyph_cache->glyphMetrics(glyphIndex, fontPixelSize);
if (!metrics.isNull() && !c.isNull()) {
metrics.width += margin * 2;
metrics.height += margin * 2;
metrics.baselineX -= margin;
metrics.baselineY += margin;
c.xMargin -= texMargin;
c.yMargin -= texMargin;
c.width += texMargin * 2;
c.height += texMargin * 2;
}
qreal x = position.x() + metrics.baselineX + m_position.x();
qreal y = position.y() - metrics.baselineY + m_position.y();
m_boundingRect |= QRectF(x, y, metrics.width, metrics.height);
float cx1 = x;
float cx2 = x + metrics.width;
float cy1 = y;
float cy2 = y + metrics.height;
float tx1 = c.x + c.xMargin;
float tx2 = tx1 + c.width;
float ty1 = c.y + c.yMargin;
float ty2 = ty1 + c.height;
if (m_baseLine.isNull())
m_baseLine = position;
int o = vp.size();
QSGGeometry::TexturedPoint2D v1;
v1.set(cx1, cy1, tx1, ty1);
QSGGeometry::TexturedPoint2D v2;
v2.set(cx2, cy1, tx2, ty1);
QSGGeometry::TexturedPoint2D v3;
v3.set(cx1, cy2, tx1, ty2);
QSGGeometry::TexturedPoint2D v4;
v4.set(cx2, cy2, tx2, ty2);
vp.append(v1);
vp.append(v2);
vp.append(v3);
vp.append(v4);
ip.append(o + 0);
ip.append(o + 2);
ip.append(o + 3);
ip.append(o + 3);
ip.append(o + 1);
ip.append(o + 0);
}
QHash<const QSGDistanceFieldGlyphCache::Texture *, GlyphInfo>::const_iterator ite = glyphsInOtherTextures.constBegin();
while (ite != glyphsInOtherTextures.constEnd()) {
QGlyphRun subNodeGlyphRun(m_glyphs);
subNodeGlyphRun.setGlyphIndexes(ite->indexes);
subNodeGlyphRun.setPositions(ite->positions);
QSGDistanceFieldGlyphNode *subNode = new QSGDistanceFieldGlyphNode(m_context);
subNode->setGlyphNodeType(SubGlyphNode);
subNode->setColor(m_color);
subNode->setStyle(m_style);
subNode->setStyleColor(m_styleColor);
subNode->setPreferredAntialiasingMode(m_antialiasingMode);
subNode->setGlyphs(m_originalPosition, subNodeGlyphRun);
subNode->update();
subNode->updateGeometry(); // we have to explicitly call this now as preprocess won't be called before it's rendered
appendChildNode(subNode);
++ite;
}
g->allocate(vp.size(), ip.size());
memcpy(g->vertexDataAsTexturedPoint2D(), vp.constData(), vp.size() * sizeof(QSGGeometry::TexturedPoint2D));
memcpy(g->indexDataAsUShort(), ip.constData(), ip.size() * sizeof(quint16));
setBoundingRect(m_boundingRect);
markDirty(DirtyGeometry);
m_dirtyGeometry = false;
m_material->setTexture(m_texture);
}
void QSGDistanceFieldGlyphNode::updateMaterial()
{
delete m_material;
if (m_style == QQuickText::Normal) {
switch (m_antialiasingMode) {
case HighQualitySubPixelAntialiasing:
m_material = new QSGHiQSubPixelDistanceFieldTextMaterial;
break;
case LowQualitySubPixelAntialiasing:
m_material = new QSGLoQSubPixelDistanceFieldTextMaterial;
break;
case GrayAntialiasing:
default:
m_material = new QSGDistanceFieldTextMaterial;
break;
}
} else {
QSGDistanceFieldStyledTextMaterial *material;
if (m_style == QQuickText::Outline) {
material = new QSGDistanceFieldOutlineTextMaterial;
} else {
QSGDistanceFieldShiftedStyleTextMaterial *sMaterial = new QSGDistanceFieldShiftedStyleTextMaterial;
if (m_style == QQuickText::Raised)
sMaterial->setShift(QPointF(0.0, 1.0));
else
sMaterial->setShift(QPointF(0.0, -1.0));
material = sMaterial;
}
material->setStyleColor(m_styleColor);
m_material = material;
}
m_material->setGlyphCache(m_glyph_cache);
if (m_glyph_cache)
m_material->setFontScale(m_glyph_cache->fontScale(m_glyphs.rawFont().pixelSize()));
m_material->setColor(m_color);
setMaterial(m_material);
m_dirtyMaterial = false;
}
QT_END_NAMESPACE