From 277d8dd8d63b8ba591956e357062a5f06fe248d6 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Thu, 4 Jan 2018 13:59:51 +0100 Subject: [PATCH] stable calculation of text base lines, so that text don't "wobble" anymore during translations of margins/paddings --- examples/colorswitch/main.cpp | 2 +- src/nodes/QskPlainTextRenderer.cpp | 71 ++++++++++++++++++++---------- src/nodes/QskRichTextRenderer.cpp | 24 ++++++++++ src/nodes/QskTextRenderer.cpp | 4 ++ support/SkinnyShortcut.cpp | 35 +++++++++++++++ support/SkinnyShortcut.h | 13 +++--- 6 files changed, 118 insertions(+), 31 deletions(-) diff --git a/examples/colorswitch/main.cpp b/examples/colorswitch/main.cpp index c110505d..02b672dc 100644 --- a/examples/colorswitch/main.cpp +++ b/examples/colorswitch/main.cpp @@ -30,7 +30,7 @@ int main( int argc, char* argv[] ) SkinnyFont::init( &app ); SkinnyShortcut::enable( SkinnyShortcut::Quit | - SkinnyShortcut::DebugShortcuts ); + SkinnyShortcut::ChangeFonts | SkinnyShortcut::DebugShortcuts ); QQmlApplicationEngine engine( QUrl( "qrc:/qml/colorswitch.qml" ) ); diff --git a/src/nodes/QskPlainTextRenderer.cpp b/src/nodes/QskPlainTextRenderer.cpp index b54c5b9c..e95bd083 100644 --- a/src/nodes/QskPlainTextRenderer.cpp +++ b/src/nodes/QskPlainTextRenderer.cpp @@ -11,6 +11,7 @@ #include #include #include +#include QSK_QT_PRIVATE_BEGIN #include @@ -43,9 +44,10 @@ QRectF QskPlainTextRenderer::textRect( const QString& text, return fm.boundingRect( r, options.textFlags(), text ); } -static qreal qskLayoutText( QTextLayout* layout, qreal lineWidth, const QskTextOptions& options ) +static qreal qskLayoutText( QTextLayout* layout, + qreal lineWidth, const QskTextOptions& options ) { - const auto maxLineCount =( options.wrapMode() == QskTextOptions::NoWrap ) + const auto maxLineCount = ( options.wrapMode() == QskTextOptions::NoWrap ) ? 1 : options.maximumLineCount(); int lineNumber = 0; @@ -60,7 +62,8 @@ static qreal qskLayoutText( QTextLayout* layout, qreal lineWidth, const QskTextO if ( lineNumber == maxLineCount ) { const auto elideMode = options.elideMode(); - auto engine = layout->engine(); + const auto engine = layout->engine(); + const auto textLength = engine->text.length(); if ( elideMode != Qt::ElideNone && textLength > characterPosition ) { @@ -72,8 +75,9 @@ static qreal qskLayoutText( QTextLayout* layout, qreal lineWidth, const QskTextO auto elidedText = engine->elidedText( elideMode, QFixed::fromReal( lineWidth ), - Qt::TextShowMnemonic, characterPosition ) - .leftJustified( textLength - characterPosition ); + Qt::TextShowMnemonic, characterPosition ); + + elidedText = elidedText.leftJustified( textLength - characterPosition ); engine->text.replace( characterPosition, elidedText.length(), elidedText ); Q_ASSERT( engine->text.length() == textLength ); @@ -88,19 +92,21 @@ static qreal qskLayoutText( QTextLayout* layout, qreal lineWidth, const QskTextO line.setPosition( QPointF( 0, y ) ); line.setLineWidth( lineWidth ); characterPosition = line.textStart() + line.textLength(); + y += line.leading() + line.height(); } + return y; } static void qskRenderText( - QQuickItem* item, QSGNode* parentNode, QTextLayout* layout, const QPointF& position, + QQuickItem* item, QSGNode* parentNode, const QTextLayout& layout, qreal baseLine, const QColor& color, QQuickText::TextStyle style, const QColor& styleColor ) { - auto rc = RenderContext::from( QOpenGLContext::currentContext() ); - auto sgContext = rc->sceneGraphContext(); + auto renderContext = RenderContext::from( QOpenGLContext::currentContext() ); + auto sgContext = renderContext->sceneGraphContext(); - // Clear out foreign nodes (e.g. from QskTextRenderer) + // Clear out foreign nodes (e.g. from QskRichTextRenderer) QSGNode* node = parentNode->firstChild(); while ( node ) { @@ -114,28 +120,34 @@ static void qskRenderText( } auto glyphNode = static_cast< QSGGlyphNode* >( parentNode->firstChild() ); - for ( int i = 0; i < layout->lineCount(); ++i ) + + const QPointF position( 0, baseLine ); + + for ( int i = 0; i < layout.lineCount(); ++i ) { - const auto glyphRuns = layout->lineAt( i ).glyphRuns(); + const auto glyphRuns = layout.lineAt( i ).glyphRuns(); + for ( const auto& glyphRun : glyphRuns ) { - const bool doCreate = !glyphNode; - if ( doCreate ) + if ( glyphNode == nullptr ) { - glyphNode = sgContext->createGlyphNode( rc, false ); // ### add native rendering to QskTextOptions? + const bool preferNativeGlyphNode = false; // QskTextOptions? + + glyphNode = sgContext->createGlyphNode( renderContext, preferNativeGlyphNode ); glyphNode->setOwnerElement( item ); glyphNode->setFlags( QSGNode::OwnedByParent | GlyphFlag ); } + glyphNode->setStyle( style ); glyphNode->setColor( color ); glyphNode->setStyleColor( styleColor ); glyphNode->setGlyphs( position, glyphRun ); glyphNode->update(); - if ( doCreate ) - { + + if ( glyphNode->parent() != parentNode ) parentNode->appendChildNode( glyphNode ); - } - glyphNode = static_cast< decltype( glyphNode ) >( glyphNode->nextSibling() ); + + glyphNode = static_cast< QSGGlyphNode* >( glyphNode->nextSibling() ); } } @@ -167,15 +179,26 @@ void QskPlainTextRenderer::updateNode( const QString& text, layout.setText( text ); layout.beginLayout(); - QPointF position; - position.ry() += qskLayoutText( &layout, rect.width(), options ); + const qreal textHeight = qskLayoutText( &layout, rect.width(), options ); layout.endLayout(); - position.setX( 0 ); - position.setY( QFontMetricsF( font ).ascent() - + ( alignment & Qt::AlignVCenter ? ( rect.height() - position.y() ) * 0.5 : 0 ) ); + qreal yBaseline = QFontMetricsF( font ).ascent(); - qskRenderText( const_cast< QQuickItem* >( item ), node, &layout, position, + 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 ); + } + + qskRenderText( const_cast< QQuickItem* >( item ), node, layout, yBaseline, colors.textColor, static_cast< QQuickText::TextStyle >( style ), colors.styleColor ); } diff --git a/src/nodes/QskRichTextRenderer.cpp b/src/nodes/QskRichTextRenderer.cpp index 8e14a3be..0a321c9c 100644 --- a/src/nodes/QskRichTextRenderer.cpp +++ b/src/nodes/QskRichTextRenderer.cpp @@ -150,6 +150,8 @@ void QskRichTextRenderer::updateNode( const QString& text, textItem.begin(); + textItem.setBottomPadding( 0 ); + textItem.setTopPadding( 0 ); textItem.setFont( font ); textItem.setOptions( options ); textItem.setAlignment( alignment ); @@ -179,6 +181,28 @@ void QskRichTextRenderer::updateNode( const QString& text, textItem.end(); + if ( alignment & Qt::AlignVCenter ) + { + /* + 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. We manipulate the layout code + by adding some padding, so that the position of base line + gets always floored. + */ + auto d = QQuickTextPrivate::get( &textItem ); + + const qreal h = d->layedOutTextRect.height() + d->lineHeightOffset(); + + if ( static_cast< int >( rect.height() - h ) % 2 ) + { + if ( static_cast( h ) % 2 ) + d->extra.value().bottomPadding = 1; + else + d->extra.value().topPadding = 1; + } + } + textItem.updateTextNode( item->window(), node ); textItem.setText( QString::null ); } diff --git a/src/nodes/QskTextRenderer.cpp b/src/nodes/QskTextRenderer.cpp index 7f8bf1d3..f22380a0 100644 --- a/src/nodes/QskTextRenderer.cpp +++ b/src/nodes/QskTextRenderer.cpp @@ -10,6 +10,10 @@ #include +/* + Since Qt 5.7 QQuickTextNode is exported as Q_QUICK_PRIVATE_EXPORT + and could be used. TODO ... + */ QSizeF QskTextRenderer::textSize( const QString& text, const QFont& font, const QskTextOptions& options ) { diff --git a/support/SkinnyShortcut.cpp b/support/SkinnyShortcut.cpp index 759df28a..73dbaf31 100644 --- a/support/SkinnyShortcut.cpp +++ b/support/SkinnyShortcut.cpp @@ -36,6 +36,18 @@ void SkinnyShortcut::enable( Types types ) cout << "CTRL-S to change the skin." << endl; } + if ( types & ChangeFonts ) + { + QskShortcutMap::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_F ), + false, &s_shortcut, [] { s_shortcut.changeFonts( +1 ); } ); + + QskShortcutMap::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_G ), + false, &s_shortcut, [] { s_shortcut.changeFonts( -1 ); } ); + + cout << "CTRL-F to increase the font size." << endl; + cout << "CTRL-G to decrease the font size." << endl; + } + if ( types & DebugBackground ) { QskShortcutMap::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_B ), @@ -134,6 +146,29 @@ void SkinnyShortcut::showBackground() } } +void SkinnyShortcut::changeFonts( int increment ) +{ + auto skin = qskSetup->skin(); + + for ( int role = 0; role <= QskSkin::HugeFont; role++ ) + { + auto font = skin->font( role ); + + if ( font.pixelSize() > 0 ) + { + font.setPixelSize( font.pixelSize() + increment ); + } + else + { + font.setPointSize( font.pointSize() + increment ); + } + + skin->setFont( role, font ); + } + + qskSetup->Q_EMIT skinChanged( skin ); +} + static inline void countNodes( const QSGNode* node, int& counter ) { if ( node ) diff --git a/support/SkinnyShortcut.h b/support/SkinnyShortcut.h index ca8b3377..7cee4aa6 100644 --- a/support/SkinnyShortcut.h +++ b/support/SkinnyShortcut.h @@ -16,13 +16,14 @@ class SKINNY_EXPORT SkinnyShortcut : public QObject public: enum Type { - Quit = 1, - RotateSkin = 2, - DebugBackground = 4, - DebugStatistics = 8, + Quit = 1 << 0, + RotateSkin = 1 << 1, + ChangeFonts = 1 << 2, + DebugBackground = 1 << 3, + DebugStatistics = 1 << 4, DebugShortcuts = DebugBackground | DebugStatistics, - AllShortcuts = Quit | RotateSkin | DebugBackground | DebugStatistics + AllShortcuts = Quit | RotateSkin | ChangeFonts | DebugBackground | DebugStatistics }; Q_ENUM( Type ) @@ -33,8 +34,8 @@ public: private: SkinnyShortcut( QObject* parent = nullptr ); -private Q_SLOTS: void rotateSkin(); + void changeFonts( int increment ); void showBackground(); void debugStatistics(); };