QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskPlainTextRenderer.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskPlainTextRenderer.h"
7#include "QskTextColors.h"
8#include "QskTextOptions.h"
9
10#include <qfontmetrics.h>
11#include <qmath.h>
12#include <qsgnode.h>
13
14QSK_QT_PRIVATE_BEGIN
15#include <private/qquickitem_p.h>
16QSK_QT_PRIVATE_END
17
18#define GlyphFlag static_cast< QSGNode::Flag >( 0x800 )
19
20QSizeF QskPlainTextRenderer::textSize(
21 const QString& text, const QFont& font, const QskTextOptions& options )
22{
23 // result differs from QQuickText::implicitSizeHint ???
24 return textRect( text, font, options, QSizeF( 10e6, 10e6 ) ).size();
25}
26
27QRectF QskPlainTextRenderer::textRect(
28 const QString& text, const QFont& font, const QskTextOptions& options,
29 const QSizeF& size )
30{
31 const QFontMetricsF fm( font );
32 const QRectF r( 0, 0, size.width(), size.height() );
33
34 return fm.boundingRect( r, options.textFlags(), text );
35}
36
37static qreal qskLayoutText( QTextLayout* layout,
38 qreal lineWidth, const QskTextOptions& options )
39{
40 if ( layout->text().isEmpty() )
41 return 0.0;
42
43 qreal y = 0;
44
45 const auto elideMode = options.effectiveElideMode();
46
47 if ( elideMode == Qt::ElideNone )
48 {
49 for ( int i = 0; i < options.maximumLineCount(); i++ )
50 {
51 auto line = layout->createLine();
52 if ( !line.isValid() )
53 break;
54
55 line.setPosition( QPointF( 0, y ) );
56 line.setLineWidth( lineWidth );
57
58 y += line.leading() + line.height();
59 }
60 }
61 else
62 {
63 const auto engine = layout->engine();
64
65 auto text = engine->elidedText(
66 elideMode, QFixed::fromReal( lineWidth ),
67 Qt::TextShowMnemonic, 0 );
68
69 // why do we need this padding ???
70 text = text.leftJustified( engine->text.length() );
71 engine->text = text;
72
73 auto line = layout->createLine();
74
75 if ( line.isValid() )
76 {
77 /*
78 For some reason the position of the text is wrong,
79 with QTextOption::NoWrap - even if word wrapping
80 for elided text does not make any sense.
81 Needs some debugging of QTextLine::layout_helper, TODO ...
82 */
83 auto option = layout->textOption();
84 option.setWrapMode( QTextOption::WrapAnywhere );
85 layout->setTextOption( option );
86
87 line.setPosition( QPointF( 0, y ) );
88 line.setLineWidth( lineWidth );
89
90 y += line.leading() + line.height();
91 }
92 }
93
94 return y;
95}
96
97static void qskRenderText(
98 QQuickItem* item, QSGNode* parentNode, const QTextLayout& layout, qreal baseLine,
99 const QColor& color, QQuickText::TextStyle style, const QColor& styleColor )
100{
101 auto renderContext = QQuickItemPrivate::get(item)->sceneGraphRenderContext();
102 auto sgContext = renderContext->sceneGraphContext();
103
104 // Clear out foreign nodes (e.g. from QskRichTextRenderer)
105 QSGNode* node = parentNode->firstChild();
106 while ( node )
107 {
108 auto sibling = node->nextSibling();
109 if ( !( node->flags() & GlyphFlag ) )
110 {
111 parentNode->removeChildNode( node );
112 delete node;
113 }
114 node = sibling;
115 }
116
117 auto glyphNode = static_cast< QSGGlyphNode* >( parentNode->firstChild() );
118
119 const QPointF position( 0, baseLine );
120
121 for ( int i = 0; i < layout.lineCount(); ++i )
122 {
123 const auto glyphRuns = layout.lineAt( i ).glyphRuns();
124
125 for ( const auto& glyphRun : glyphRuns )
126 {
127 if ( glyphNode == nullptr )
128 {
129 const bool preferNativeGlyphNode = false; // QskTextOptions?
130 constexpr int renderQuality = -1; // QQuickText::DefaultRenderTypeQuality
131
132#if QT_VERSION >= QT_VERSION_CHECK( 6, 7, 0 )
133 const auto renderType = preferNativeGlyphNode
134 ? QSGTextNode::QtRendering : QSGTextNode::NativeRendering;
135 glyphNode = sgContext->createGlyphNode(
136 renderContext, renderType, renderQuality );
137#elif QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
138 glyphNode = sgContext->createGlyphNode(
139 renderContext, preferNativeGlyphNode, renderQuality );
140#else
141 Q_UNUSED( renderQuality );
142 glyphNode = sgContext->createGlyphNode(
143 renderContext, preferNativeGlyphNode );
144#endif
145
146#if QT_VERSION < QT_VERSION_CHECK( 6, 7, 0 )
147 glyphNode->setOwnerElement( item );
148#endif
149
150 glyphNode->setFlags( QSGNode::OwnedByParent | GlyphFlag );
151 }
152
153 glyphNode->setStyle( style );
154 glyphNode->setColor( color );
155 glyphNode->setStyleColor( styleColor );
156 glyphNode->setGlyphs( position, glyphRun );
157 glyphNode->update();
158
159 if ( glyphNode->parent() != parentNode )
160 parentNode->appendChildNode( glyphNode );
161
162 glyphNode = static_cast< QSGGlyphNode* >( glyphNode->nextSibling() );
163 }
164 }
165
166 // Remove leftover glyphs
167 while ( glyphNode )
168 {
169 auto sibling = glyphNode->nextSibling();
170 if ( glyphNode->flags() & GlyphFlag )
171 {
172 parentNode->removeChildNode( glyphNode );
173 delete glyphNode;
174 }
175 glyphNode = static_cast< QSGGlyphNode* >( sibling );
176 }
177}
178
179void QskPlainTextRenderer::updateNode( const QString& text,
180 const QFont& font, const QskTextOptions& options,
181 Qsk::TextStyle style, const QskTextColors& colors,
182 Qt::Alignment alignment, const QRectF& rect,
183 const QQuickItem* item, QSGTransformNode* node )
184{
185 QTextOption textOption( alignment );
186 textOption.setWrapMode( static_cast< QTextOption::WrapMode >( options.wrapMode() ) );
187
188 QString tmp = text;
189
190#if 0
191 const int pos = tmp.indexOf( QLatin1Char( '\x9c' ) );
192 if ( pos != -1 )
193 {
194 // ST: string termination
195
196 tmp = tmp.mid( 0, pos );
197 tmp.replace( QLatin1Char( '\n' ), QChar::LineSeparator );
198 }
199 else
200#endif
201 if ( tmp.contains( QLatin1Char( '\n' ) ) )
202 {
203 tmp.replace( QLatin1Char('\n'), QChar::LineSeparator );
204 }
205
206
207 QTextLayout layout;
208 layout.setFont( font );
209 layout.setTextOption( textOption );
210 layout.setText( tmp );
211
212 layout.beginLayout();
213 const qreal textHeight = qskLayoutText( &layout, rect.width(), options );
214 layout.endLayout();
215
216 const qreal y0 = QFontMetricsF( font ).ascent();
217
218 qreal yBaseline = y0;
219
220 if ( alignment & Qt::AlignVCenter )
221 {
222 yBaseline += ( rect.height() - textHeight ) * 0.5;
223 }
224 else if ( alignment & Qt::AlignBottom )
225 {
226 yBaseline += rect.height() - textHeight;
227 }
228
229 if ( yBaseline != y0 )
230 {
231 /*
232 We need to have a stable algo for rounding the text base line,
233 so that texts don't start wobbling, when processing transitions
234 between margins/paddings.
235 */
236
237 const int bh = int( layout.boundingRect().height() );
238 yBaseline = ( bh % 2 ) ? qFloor( yBaseline ) : qCeil( yBaseline );
239 }
240
241 qskRenderText(
242 const_cast< QQuickItem* >( item ), node, layout, yBaseline,
243 colors.textColor, static_cast< QQuickText::TextStyle >( style ),
244 colors.styleColor );
245}
246
247void QskPlainTextRenderer::updateNodeColor(
248 QSGNode* parentNode, const QColor& textColor,
249 Qsk::TextStyle style, const QColor& styleColor )
250{
251 auto glyphNode = static_cast< QSGGlyphNode* >( parentNode->firstChild() );
252 while ( glyphNode )
253 {
254 if ( glyphNode->flags() & GlyphFlag )
255 {
256 glyphNode->setColor( textColor );
257 glyphNode->setStyle( static_cast< QQuickText::TextStyle >( style ) );
258 glyphNode->setStyleColor( styleColor );
259 glyphNode->update();
260 }
261 glyphNode = static_cast< QSGGlyphNode* >( glyphNode->nextSibling() );
262 }
263}