stable calculation of text base lines, so that text don't "wobble"

anymore during translations of margins/paddings
This commit is contained in:
Uwe Rathmann 2018-01-04 13:59:51 +01:00
parent 6108af2c84
commit 277d8dd8d6
6 changed files with 118 additions and 31 deletions

View File

@ -30,7 +30,7 @@ int main( int argc, char* argv[] )
SkinnyFont::init( &app ); SkinnyFont::init( &app );
SkinnyShortcut::enable( SkinnyShortcut::Quit | SkinnyShortcut::enable( SkinnyShortcut::Quit |
SkinnyShortcut::DebugShortcuts ); SkinnyShortcut::ChangeFonts | SkinnyShortcut::DebugShortcuts );
QQmlApplicationEngine engine( QUrl( "qrc:/qml/colorswitch.qml" ) ); QQmlApplicationEngine engine( QUrl( "qrc:/qml/colorswitch.qml" ) );

View File

@ -11,6 +11,7 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QSGTransformNode> #include <QSGTransformNode>
#include <QQuickWindow> #include <QQuickWindow>
#include <QtMath>
QSK_QT_PRIVATE_BEGIN QSK_QT_PRIVATE_BEGIN
#include <private/qsgadaptationlayer_p.h> #include <private/qsgadaptationlayer_p.h>
@ -43,9 +44,10 @@ QRectF QskPlainTextRenderer::textRect( const QString& text,
return fm.boundingRect( r, options.textFlags(), 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(); ? 1 : options.maximumLineCount();
int lineNumber = 0; int lineNumber = 0;
@ -60,7 +62,8 @@ static qreal qskLayoutText( QTextLayout* layout, qreal lineWidth, const QskTextO
if ( lineNumber == maxLineCount ) if ( lineNumber == maxLineCount )
{ {
const auto elideMode = options.elideMode(); const auto elideMode = options.elideMode();
auto engine = layout->engine(); const auto engine = layout->engine();
const auto textLength = engine->text.length(); const auto textLength = engine->text.length();
if ( elideMode != Qt::ElideNone && textLength > characterPosition ) if ( elideMode != Qt::ElideNone && textLength > characterPosition )
{ {
@ -72,8 +75,9 @@ static qreal qskLayoutText( QTextLayout* layout, qreal lineWidth, const QskTextO
auto elidedText = engine->elidedText( auto elidedText = engine->elidedText(
elideMode, QFixed::fromReal( lineWidth ), elideMode, QFixed::fromReal( lineWidth ),
Qt::TextShowMnemonic, characterPosition ) Qt::TextShowMnemonic, characterPosition );
.leftJustified( textLength - characterPosition );
elidedText = elidedText.leftJustified( textLength - characterPosition );
engine->text.replace( characterPosition, elidedText.length(), elidedText ); engine->text.replace( characterPosition, elidedText.length(), elidedText );
Q_ASSERT( engine->text.length() == textLength ); 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.setPosition( QPointF( 0, y ) );
line.setLineWidth( lineWidth ); line.setLineWidth( lineWidth );
characterPosition = line.textStart() + line.textLength(); characterPosition = line.textStart() + line.textLength();
y += line.leading() + line.height(); y += line.leading() + line.height();
} }
return y; return y;
} }
static void qskRenderText( 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 ) const QColor& color, QQuickText::TextStyle style, const QColor& styleColor )
{ {
auto rc = RenderContext::from( QOpenGLContext::currentContext() ); auto renderContext = RenderContext::from( QOpenGLContext::currentContext() );
auto sgContext = rc->sceneGraphContext(); auto sgContext = renderContext->sceneGraphContext();
// Clear out foreign nodes (e.g. from QskTextRenderer) // Clear out foreign nodes (e.g. from QskRichTextRenderer)
QSGNode* node = parentNode->firstChild(); QSGNode* node = parentNode->firstChild();
while ( node ) while ( node )
{ {
@ -114,28 +120,34 @@ static void qskRenderText(
} }
auto glyphNode = static_cast< QSGGlyphNode* >( parentNode->firstChild() ); 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 ) for ( const auto& glyphRun : glyphRuns )
{ {
const bool doCreate = !glyphNode; if ( glyphNode == nullptr )
if ( doCreate )
{ {
glyphNode = sgContext->createGlyphNode( rc, false ); // ### add native rendering to QskTextOptions? const bool preferNativeGlyphNode = false; // QskTextOptions?
glyphNode = sgContext->createGlyphNode( renderContext, preferNativeGlyphNode );
glyphNode->setOwnerElement( item ); glyphNode->setOwnerElement( item );
glyphNode->setFlags( QSGNode::OwnedByParent | GlyphFlag ); glyphNode->setFlags( QSGNode::OwnedByParent | GlyphFlag );
} }
glyphNode->setStyle( style ); glyphNode->setStyle( style );
glyphNode->setColor( color ); glyphNode->setColor( color );
glyphNode->setStyleColor( styleColor ); glyphNode->setStyleColor( styleColor );
glyphNode->setGlyphs( position, glyphRun ); glyphNode->setGlyphs( position, glyphRun );
glyphNode->update(); glyphNode->update();
if ( doCreate )
{ if ( glyphNode->parent() != parentNode )
parentNode->appendChildNode( glyphNode ); 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.setText( text );
layout.beginLayout(); layout.beginLayout();
QPointF position; const qreal textHeight = qskLayoutText( &layout, rect.width(), options );
position.ry() += qskLayoutText( &layout, rect.width(), options );
layout.endLayout(); layout.endLayout();
position.setX( 0 ); qreal yBaseline = QFontMetricsF( font ).ascent();
position.setY( QFontMetricsF( font ).ascent()
+ ( alignment & Qt::AlignVCenter ? ( rect.height() - position.y() ) * 0.5 : 0 ) );
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 ); colors.textColor, static_cast< QQuickText::TextStyle >( style ), colors.styleColor );
} }

View File

@ -150,6 +150,8 @@ void QskRichTextRenderer::updateNode( const QString& text,
textItem.begin(); textItem.begin();
textItem.setBottomPadding( 0 );
textItem.setTopPadding( 0 );
textItem.setFont( font ); textItem.setFont( font );
textItem.setOptions( options ); textItem.setOptions( options );
textItem.setAlignment( alignment ); textItem.setAlignment( alignment );
@ -179,6 +181,28 @@ void QskRichTextRenderer::updateNode( const QString& text,
textItem.end(); 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<int>( h ) % 2 )
d->extra.value().bottomPadding = 1;
else
d->extra.value().topPadding = 1;
}
}
textItem.updateTextNode( item->window(), node ); textItem.updateTextNode( item->window(), node );
textItem.setText( QString::null ); textItem.setText( QString::null );
} }

View File

@ -10,6 +10,10 @@
#include <QRectF> #include <QRectF>
/*
Since Qt 5.7 QQuickTextNode is exported as Q_QUICK_PRIVATE_EXPORT
and could be used. TODO ...
*/
QSizeF QskTextRenderer::textSize( const QString& text, QSizeF QskTextRenderer::textSize( const QString& text,
const QFont& font, const QskTextOptions& options ) const QFont& font, const QskTextOptions& options )
{ {

View File

@ -36,6 +36,18 @@ void SkinnyShortcut::enable( Types types )
cout << "CTRL-S to change the skin." << endl; 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 ) if ( types & DebugBackground )
{ {
QskShortcutMap::addShortcut( QKeySequence( Qt::CTRL + Qt::Key_B ), 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 ) static inline void countNodes( const QSGNode* node, int& counter )
{ {
if ( node ) if ( node )

View File

@ -16,13 +16,14 @@ class SKINNY_EXPORT SkinnyShortcut : public QObject
public: public:
enum Type enum Type
{ {
Quit = 1, Quit = 1 << 0,
RotateSkin = 2, RotateSkin = 1 << 1,
DebugBackground = 4, ChangeFonts = 1 << 2,
DebugStatistics = 8, DebugBackground = 1 << 3,
DebugStatistics = 1 << 4,
DebugShortcuts = DebugBackground | DebugStatistics, DebugShortcuts = DebugBackground | DebugStatistics,
AllShortcuts = Quit | RotateSkin | DebugBackground | DebugStatistics AllShortcuts = Quit | RotateSkin | ChangeFonts | DebugBackground | DebugStatistics
}; };
Q_ENUM( Type ) Q_ENUM( Type )
@ -33,8 +34,8 @@ public:
private: private:
SkinnyShortcut( QObject* parent = nullptr ); SkinnyShortcut( QObject* parent = nullptr );
private Q_SLOTS:
void rotateSkin(); void rotateSkin();
void changeFonts( int increment );
void showBackground(); void showBackground();
void debugStatistics(); void debugStatistics();
}; };