2017-07-21 18:21:34 +02:00
|
|
|
/******************************************************************************
|
|
|
|
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
|
|
|
* This file may be used under the terms of the QSkinny License, Version 1.0
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
#include "QskPlainTextRenderer.h"
|
2017-10-20 20:26:39 +02:00
|
|
|
#include "QskTextColors.h"
|
2017-10-23 07:46:46 +02:00
|
|
|
#include "QskTextOptions.h"
|
2017-07-21 18:21:34 +02:00
|
|
|
|
2018-07-19 14:10:48 +02:00
|
|
|
#include <qfontmetrics.h>
|
|
|
|
#include <qsgnode.h>
|
|
|
|
#include <qmath.h>
|
2017-07-21 18:21:34 +02:00
|
|
|
|
|
|
|
QSK_QT_PRIVATE_BEGIN
|
|
|
|
#include <private/qsgadaptationlayer_p.h>
|
|
|
|
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK( 5, 8, 0 )
|
|
|
|
#include <private/qsgcontext_p.h>
|
|
|
|
typedef QSGRenderContext RenderContext;
|
|
|
|
#else
|
|
|
|
#include <private/qsgdefaultrendercontext_p.h>
|
|
|
|
typedef QSGDefaultRenderContext RenderContext;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
QSK_QT_PRIVATE_END
|
|
|
|
|
|
|
|
#define GlyphFlag static_cast< QSGNode::Flag >( 0x800 )
|
|
|
|
|
2017-10-23 07:46:46 +02:00
|
|
|
QSizeF QskPlainTextRenderer::textSize( const QString& text,
|
|
|
|
const QFont& font, const QskTextOptions& options )
|
2017-07-21 18:21:34 +02:00
|
|
|
{
|
|
|
|
// result differs from QskTextRenderer::implicitSizeHint ???
|
2017-10-23 07:46:46 +02:00
|
|
|
return textRect( text, font, options, QSizeF( 10e6, 10e6 ) ).size();
|
2017-07-21 18:21:34 +02:00
|
|
|
}
|
|
|
|
|
2017-10-23 07:46:46 +02:00
|
|
|
QRectF QskPlainTextRenderer::textRect( const QString& text,
|
|
|
|
const QFont& font, const QskTextOptions& options, const QSizeF& size )
|
2017-07-21 18:21:34 +02:00
|
|
|
{
|
2017-10-23 07:46:46 +02:00
|
|
|
const QFontMetricsF fm( font );
|
2017-12-07 17:04:05 +01:00
|
|
|
const QRectF r( 0, 0, size.width(), size.height() );
|
2017-07-21 18:21:34 +02:00
|
|
|
|
2017-10-23 07:46:46 +02:00
|
|
|
return fm.boundingRect( r, options.textFlags(), text );
|
2017-07-21 18:21:34 +02:00
|
|
|
}
|
|
|
|
|
2018-01-04 13:59:51 +01:00
|
|
|
static qreal qskLayoutText( QTextLayout* layout,
|
|
|
|
qreal lineWidth, const QskTextOptions& options )
|
2017-07-21 18:21:34 +02:00
|
|
|
{
|
2018-01-04 13:59:51 +01:00
|
|
|
const auto maxLineCount = ( options.wrapMode() == QskTextOptions::NoWrap )
|
2017-07-21 18:21:34 +02:00
|
|
|
? 1 : options.maximumLineCount();
|
2018-01-03 17:24:27 +01:00
|
|
|
|
2017-07-21 18:21:34 +02:00
|
|
|
int lineNumber = 0;
|
|
|
|
int characterPosition = 0;
|
|
|
|
qreal y = 0;
|
2018-01-03 17:24:27 +01:00
|
|
|
|
2017-07-21 18:21:34 +02:00
|
|
|
Q_FOREVER
|
|
|
|
{
|
|
|
|
if ( ++lineNumber > maxLineCount )
|
|
|
|
break;
|
|
|
|
|
|
|
|
if ( lineNumber == maxLineCount )
|
|
|
|
{
|
|
|
|
const auto elideMode = options.elideMode();
|
2018-01-04 13:59:51 +01:00
|
|
|
const auto engine = layout->engine();
|
|
|
|
|
2017-07-21 18:21:34 +02:00
|
|
|
const auto textLength = engine->text.length();
|
|
|
|
if ( elideMode != Qt::ElideNone && textLength > characterPosition )
|
|
|
|
{
|
|
|
|
if ( lineNumber == 1 || elideMode == Qt::ElideRight )
|
|
|
|
{
|
|
|
|
auto option = layout->textOption();
|
|
|
|
option.setWrapMode( QTextOption::WrapAnywhere );
|
|
|
|
layout->setTextOption( option );
|
|
|
|
|
|
|
|
auto elidedText = engine->elidedText(
|
|
|
|
elideMode, QFixed::fromReal( lineWidth ),
|
2018-01-04 13:59:51 +01:00
|
|
|
Qt::TextShowMnemonic, characterPosition );
|
|
|
|
|
|
|
|
elidedText = elidedText.leftJustified( textLength - characterPosition );
|
2017-07-21 18:21:34 +02:00
|
|
|
|
|
|
|
engine->text.replace( characterPosition, elidedText.length(), elidedText );
|
|
|
|
Q_ASSERT( engine->text.length() == textLength );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto line = layout->createLine();
|
|
|
|
if ( !line.isValid() )
|
|
|
|
break;
|
|
|
|
|
2018-01-03 17:24:27 +01:00
|
|
|
line.setPosition( QPointF( 0, y ) );
|
2017-07-21 18:21:34 +02:00
|
|
|
line.setLineWidth( lineWidth );
|
|
|
|
characterPosition = line.textStart() + line.textLength();
|
2018-01-04 13:59:51 +01:00
|
|
|
|
2017-07-21 18:21:34 +02:00
|
|
|
y += line.leading() + line.height();
|
|
|
|
}
|
2018-01-04 13:59:51 +01:00
|
|
|
|
2017-07-21 18:21:34 +02:00
|
|
|
return y;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void qskRenderText(
|
2018-01-04 13:59:51 +01:00
|
|
|
QQuickItem* item, QSGNode* parentNode, const QTextLayout& layout, qreal baseLine,
|
2017-07-21 18:21:34 +02:00
|
|
|
const QColor& color, QQuickText::TextStyle style, const QColor& styleColor )
|
|
|
|
{
|
2018-01-04 13:59:51 +01:00
|
|
|
auto renderContext = RenderContext::from( QOpenGLContext::currentContext() );
|
|
|
|
auto sgContext = renderContext->sceneGraphContext();
|
2017-07-21 18:21:34 +02:00
|
|
|
|
2018-01-04 13:59:51 +01:00
|
|
|
// Clear out foreign nodes (e.g. from QskRichTextRenderer)
|
2017-07-21 18:21:34 +02:00
|
|
|
QSGNode* node = parentNode->firstChild();
|
|
|
|
while ( node )
|
|
|
|
{
|
|
|
|
auto sibling = node->nextSibling();
|
|
|
|
if ( !( node->flags() & GlyphFlag ) )
|
|
|
|
{
|
|
|
|
parentNode->removeChildNode( node );
|
|
|
|
delete node;
|
|
|
|
}
|
|
|
|
node = sibling;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto glyphNode = static_cast< QSGGlyphNode* >( parentNode->firstChild() );
|
2018-01-04 13:59:51 +01:00
|
|
|
|
|
|
|
const QPointF position( 0, baseLine );
|
|
|
|
|
|
|
|
for ( int i = 0; i < layout.lineCount(); ++i )
|
2017-07-21 18:21:34 +02:00
|
|
|
{
|
2018-01-04 13:59:51 +01:00
|
|
|
const auto glyphRuns = layout.lineAt( i ).glyphRuns();
|
|
|
|
|
2017-10-30 14:38:30 +01:00
|
|
|
for ( const auto& glyphRun : glyphRuns )
|
2017-07-21 18:21:34 +02:00
|
|
|
{
|
2018-01-04 13:59:51 +01:00
|
|
|
if ( glyphNode == nullptr )
|
2017-07-21 18:21:34 +02:00
|
|
|
{
|
2018-01-04 13:59:51 +01:00
|
|
|
const bool preferNativeGlyphNode = false; // QskTextOptions?
|
|
|
|
|
|
|
|
glyphNode = sgContext->createGlyphNode( renderContext, preferNativeGlyphNode );
|
2017-07-21 18:21:34 +02:00
|
|
|
glyphNode->setOwnerElement( item );
|
|
|
|
glyphNode->setFlags( QSGNode::OwnedByParent | GlyphFlag );
|
|
|
|
}
|
2018-01-04 13:59:51 +01:00
|
|
|
|
2017-07-21 18:21:34 +02:00
|
|
|
glyphNode->setStyle( style );
|
|
|
|
glyphNode->setColor( color );
|
|
|
|
glyphNode->setStyleColor( styleColor );
|
|
|
|
glyphNode->setGlyphs( position, glyphRun );
|
|
|
|
glyphNode->update();
|
2018-01-04 13:59:51 +01:00
|
|
|
|
|
|
|
if ( glyphNode->parent() != parentNode )
|
2017-07-21 18:21:34 +02:00
|
|
|
parentNode->appendChildNode( glyphNode );
|
2018-01-04 13:59:51 +01:00
|
|
|
|
|
|
|
glyphNode = static_cast< QSGGlyphNode* >( glyphNode->nextSibling() );
|
2017-07-21 18:21:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove leftover glyphs
|
|
|
|
while ( glyphNode )
|
|
|
|
{
|
|
|
|
auto sibling = glyphNode->nextSibling();
|
|
|
|
if ( glyphNode->flags() & GlyphFlag )
|
|
|
|
{
|
|
|
|
parentNode->removeChildNode( glyphNode );
|
|
|
|
delete glyphNode;
|
|
|
|
}
|
|
|
|
glyphNode = static_cast< QSGGlyphNode* >( sibling );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-20 20:26:39 +02:00
|
|
|
void QskPlainTextRenderer::updateNode( const QString& text,
|
2017-10-23 07:46:46 +02:00
|
|
|
const QFont& font, const QskTextOptions& options,
|
|
|
|
Qsk::TextStyle style, const QskTextColors& colors,
|
|
|
|
Qt::Alignment alignment, const QRectF& rect,
|
2017-10-20 20:26:39 +02:00
|
|
|
const QQuickItem* item, QSGTransformNode* node )
|
2017-07-21 18:21:34 +02:00
|
|
|
{
|
2017-10-23 07:46:46 +02:00
|
|
|
QTextOption textOption( alignment );
|
|
|
|
textOption.setWrapMode( static_cast< QTextOption::WrapMode >( options.wrapMode() ) );
|
2017-07-21 18:21:34 +02:00
|
|
|
|
|
|
|
QTextLayout layout;
|
2017-10-23 07:46:46 +02:00
|
|
|
layout.setFont( font );
|
2017-07-21 18:21:34 +02:00
|
|
|
layout.setTextOption( textOption );
|
|
|
|
layout.setText( text );
|
|
|
|
|
|
|
|
layout.beginLayout();
|
2018-01-04 13:59:51 +01:00
|
|
|
const qreal textHeight = qskLayoutText( &layout, rect.width(), options );
|
2017-07-21 18:21:34 +02:00
|
|
|
layout.endLayout();
|
|
|
|
|
2018-01-04 13:59:51 +01:00
|
|
|
qreal yBaseline = QFontMetricsF( font ).ascent();
|
|
|
|
|
|
|
|
if ( alignment & Qt::AlignVCenter )
|
|
|
|
{
|
|
|
|
yBaseline += ( rect.height() - textHeight ) * 0.5;
|
|
|
|
|
|
|
|
/*
|
|
|
|
We need to have a stable algo for rounding the text base line,
|
|
|
|
so that texts don't start wobbling, when processing transitions
|
|
|
|
between margins/paddings.
|
|
|
|
*/
|
|
|
|
|
|
|
|
const int bh = int( layout.boundingRect().height() );
|
|
|
|
yBaseline = ( bh % 2 ) ? qFloor( yBaseline ) : qCeil( yBaseline );
|
|
|
|
}
|
2017-07-21 18:21:34 +02:00
|
|
|
|
2018-01-04 13:59:51 +01:00
|
|
|
qskRenderText( const_cast< QQuickItem* >( item ), node, layout, yBaseline,
|
2017-10-20 20:26:39 +02:00
|
|
|
colors.textColor, static_cast< QQuickText::TextStyle >( style ), colors.styleColor );
|
|
|
|
}
|
|
|
|
|
2017-07-21 18:21:34 +02:00
|
|
|
void QskPlainTextRenderer::updateNodeColor( QSGNode* parentNode, const QColor& textColor,
|
|
|
|
Qsk::TextStyle style, const QColor& styleColor )
|
|
|
|
{
|
|
|
|
auto glyphNode = static_cast< QSGGlyphNode* >( parentNode->firstChild() );
|
|
|
|
while ( glyphNode )
|
|
|
|
{
|
|
|
|
if ( glyphNode->flags() & GlyphFlag )
|
|
|
|
{
|
|
|
|
glyphNode->setColor( textColor );
|
|
|
|
glyphNode->setStyle( static_cast< QQuickText::TextStyle >( style ) );
|
|
|
|
glyphNode->setStyleColor( styleColor );
|
|
|
|
glyphNode->update();
|
|
|
|
}
|
|
|
|
glyphNode = static_cast< QSGGlyphNode* >( glyphNode->nextSibling() );
|
|
|
|
}
|
|
|
|
}
|