QGIS API Documentation 3.41.0-Master (af5edcb665c)
Loading...
Searching...
No Matches
qgstextlabelfeature.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgstextlabelfeature.cpp
3 ---------------------
4 begin : December 2015
5 copyright : (C) 2015 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgstextlabelfeature.h"
17
18#include "qgspallabeling.h"
19#include "qgsmaptopixel.h"
21#include "qgstextfragment.h"
22#include "qgstextblock.h"
23#include "qgstextrenderer.h"
24#include "qgsrendercontext.h"
25
26QgsTextLabelFeature::QgsTextLabelFeature( QgsFeatureId id, geos::unique_ptr geometry, QSizeF size )
27 : QgsLabelFeature( id, std::move( geometry ), size )
28{
29 mDefinedFont = QFont();
30}
31
33
34QString QgsTextLabelFeature::text( int partId ) const
35{
36 if ( partId == -1 )
37 return mLabelText;
38 else
39 return mTextMetrics->grapheme( partId );
40}
41
43{
44 return mTextMetrics.has_value() ? mTextMetrics->graphemeFormat( partId ) : QgsTextCharacterFormat();
45}
46
48{
49 return mTextMetrics.has_value() && partId < mTextMetrics->graphemeFormatCount();
50}
51
52QgsPrecalculatedTextMetrics QgsTextLabelFeature::calculateTextMetrics( const QgsMapToPixel *xform, const QgsRenderContext &context, const QgsTextFormat &format, const QFont &baseFont, const QFontMetricsF &fontMetrics, double letterSpacing, double wordSpacing, const QString &text, QgsTextDocument *document, QgsTextDocumentMetrics * )
53{
54 const double tabStopDistancePainterUnits = format.tabStopDistanceUnit() == Qgis::RenderUnit::Percentage
55 ? format.tabStopDistance() * baseFont.pixelSize()
57
58 const QList< QgsTextFormat::Tab > tabPositions = format.tabPositions();
59 QList< double > tabStopDistancesPainterUnits;
60 tabStopDistancesPainterUnits.reserve( tabPositions.size() );
61 for ( const QgsTextFormat::Tab &tab : tabPositions )
62 {
63 tabStopDistancesPainterUnits.append(
65 ? tab.position() * baseFont.pixelSize()
66 : context.convertToPainterUnits( tab.position(), format.tabStopDistanceUnit(), format.tabStopDistanceMapUnitScale() )
67 );
68 }
69
70
71 // create label info!
72 const double mapScale = xform->mapUnitsPerPixel();
73 QStringList graphemes;
74 QVector< QgsTextCharacterFormat > graphemeFormats;
75
76 if ( document )
77 {
78 for ( const QgsTextBlock &block : std::as_const( *document ) )
79 {
80 for ( const QgsTextFragment &fragment : block )
81 {
82 const QStringList fragmentGraphemes = QgsPalLabeling::splitToGraphemes( fragment.text() );
83 for ( const QString &grapheme : fragmentGraphemes )
84 {
85 graphemes.append( grapheme );
86 graphemeFormats.append( fragment.characterFormat() );
87 }
88 }
89 }
90 }
91 else
92 {
93 //split string by valid grapheme boundaries - required for certain scripts (see #6883)
95 }
96
97 QVector< double > characterWidths( graphemes.count() );
98 QVector< double > characterHeights( graphemes.count() );
99 QVector< double > characterDescents( graphemes.count() );
100
101 QFont previousNonSuperSubScriptFont;
102
103 double currentWidth = 0;
104 for ( int i = 0; i < graphemes.count(); i++ )
105 {
106 // reconstruct how Qt creates word spacing, then adjust per individual stored character
107 // this will allow PAL to create each candidate width = character width + correct spacing
108
109 double graphemeFirstCharHorizontalAdvanceWithLetterSpacing = 0;
110 double graphemeFirstCharHorizontalAdvance = 0;
111 double graphemeHorizontalAdvance = 0;
112 double characterDescent = 0;
113 double characterHeight = 0;
114 if ( const QgsTextCharacterFormat *graphemeFormat = !graphemeFormats.empty() ? &graphemeFormats[i] : nullptr )
115 {
116 QFont graphemeFont = baseFont;
117 graphemeFormat->updateFontForFormat( graphemeFont, context, 1 );
118
119 if ( i == 0 )
120 previousNonSuperSubScriptFont = graphemeFont;
121
122 if ( graphemeFormat->hasVerticalAlignmentSet() )
123 {
124 switch ( graphemeFormat->verticalAlignment() )
125 {
127 previousNonSuperSubScriptFont = graphemeFont;
128 break;
129
132 {
133 if ( graphemeFormat->fontPointSize() < 0 )
134 {
135 // if fragment has no explicit font size set, then we scale the inherited font size to 60% of base font size
136 // this allows for easier use of super/subscript in labels as "my text<sup>2</sup>" will automatically render
137 // the superscript in a smaller font size. BUT if the fragment format HAS a non -1 font size then it indicates
138 // that the document has an explicit font size for the super/subscript element, eg "my text<sup style="font-size: 6pt">2</sup>"
139 // which we should respect
140 graphemeFont.setPixelSize( static_cast< int >( std::round( graphemeFont.pixelSize() * QgsTextRenderer::SUPERSCRIPT_SUBSCRIPT_FONT_SIZE_SCALING_FACTOR ) ) );
141 }
142 break;
143 }
144 }
145 }
146 else
147 {
148 previousNonSuperSubScriptFont = graphemeFont;
149 }
150
151 const QFontMetricsF graphemeFontMetrics( graphemeFont );
152 graphemeFirstCharHorizontalAdvance = graphemeFontMetrics.horizontalAdvance( QString( graphemes[i].at( 0 ) ) );
153 graphemeFirstCharHorizontalAdvanceWithLetterSpacing = graphemeFontMetrics.horizontalAdvance( graphemes[i].at( 0 ) ) + letterSpacing;
154 graphemeHorizontalAdvance = graphemeFontMetrics.horizontalAdvance( QString( graphemes[i] ) );
155 characterDescent = graphemeFontMetrics.descent();
156 characterHeight = graphemeFontMetrics.height();
157 }
158 else if ( graphemes[i] == '\t' )
159 {
160 double nextTabStop = 0;
161 if ( !tabStopDistancesPainterUnits.empty() )
162 {
163 // if we don't find a tab stop before the current length of line, we just ignore the tab character entirely
164 nextTabStop = currentWidth;
165 for ( const double tabStop : std::as_const( tabStopDistancesPainterUnits ) )
166 {
167 if ( tabStop >= currentWidth )
168 {
169 nextTabStop = tabStop;
170 break;
171 }
172 }
173 }
174 else
175 {
176 nextTabStop = ( std::floor( currentWidth / tabStopDistancePainterUnits ) + 1 ) * tabStopDistancePainterUnits;
177 }
178
179 const double thisTabWidth = nextTabStop - currentWidth;
180
181 graphemeFirstCharHorizontalAdvance = thisTabWidth;
182 graphemeFirstCharHorizontalAdvanceWithLetterSpacing = thisTabWidth;
183 graphemeHorizontalAdvance = thisTabWidth;
184 characterDescent = fontMetrics.descent();
185 characterHeight = fontMetrics.height();
186 }
187 else
188 {
189 graphemeFirstCharHorizontalAdvance = fontMetrics.horizontalAdvance( QString( graphemes[i].at( 0 ) ) );
190 graphemeFirstCharHorizontalAdvanceWithLetterSpacing = fontMetrics.horizontalAdvance( graphemes[i].at( 0 ) ) + letterSpacing;
191 graphemeHorizontalAdvance = fontMetrics.horizontalAdvance( QString( graphemes[i] ) );
192 characterDescent = fontMetrics.descent();
193 characterHeight = fontMetrics.height();
194 }
195
196 qreal wordSpaceFix = qreal( 0.0 );
197 if ( graphemes[i] == QLatin1String( " " ) )
198 {
199 // word spacing only gets added once at end of consecutive run of spaces, see QTextEngine::shapeText()
200 int nxt = i + 1;
201 wordSpaceFix = ( nxt < graphemes.count() && graphemes[nxt] != QLatin1String( " " ) ) ? wordSpacing : qreal( 0.0 );
202 }
203
204 // this workaround only works for clusters with a single character. Not sure how it should be handled
205 // with multi-character clusters.
206 if ( graphemes[i].length() == 1 &&
207 !qgsDoubleNear( graphemeFirstCharHorizontalAdvance, graphemeFirstCharHorizontalAdvanceWithLetterSpacing ) )
208 {
209 // word spacing applied when it shouldn't be
210 wordSpaceFix -= wordSpacing;
211 }
212
213 const double charWidth = graphemeHorizontalAdvance + wordSpaceFix;
214 characterWidths[i] = mapScale * charWidth;
215 characterHeights[i] = mapScale * characterHeight;
216 characterDescents[i] = mapScale * characterDescent;
217
218 currentWidth += charWidth;
219 }
220
221 QgsPrecalculatedTextMetrics res( graphemes, std::move( characterWidths ), std::move( characterHeights ), std::move( characterDescents ) );
222 res.setGraphemeFormats( graphemeFormats );
223 return res;
224}
225
227{
229 mDocumentMetrics = metrics;
230}
@ Normal
Adjacent characters are positioned in the standard way for text in the writing system in use.
@ SubScript
Characters are placed below the base line for normal text.
@ SuperScript
Characters are placed above the base line for normal text.
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size)
The QgsLabelFeature class describes a feature that should be used within the labeling engine.
QString mLabelText
text of the label
Perform transforms between map coordinates and device coordinates.
double mapUnitsPerPixel() const
Returns the current map units per pixel.
static QStringList splitToGraphemes(const QString &text)
Splits a text string to a list of graphemes, which are the smallest allowable character divisions in ...
Contains precalculated properties regarding text metrics for text to be renderered at a later stage.
void setGraphemeFormats(const QVector< QgsTextCharacterFormat > &formats)
Sets the character formats associated with the text graphemes().
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
Represents a block of text consisting of one or more QgsTextFragment objects.
Stores information relating to individual character formatting.
Contains pre-calculated metrics of a QgsTextDocument.
Represents a document consisting of one or more QgsTextBlock objects.
Defines a tab position for a text format.
Container for all settings relating to text rendering.
QList< QgsTextFormat::Tab > tabPositions() const
Returns the list of tab positions for tab stops.
double tabStopDistance() const
Returns the distance for tab stops.
Qgis::RenderUnit tabStopDistanceUnit() const
Returns the units for the tab stop distance.
QgsMapUnitScale tabStopDistanceMapUnitScale() const
Returns the map unit scale object for the tab stop distance.
Stores a fragment of document along with formatting overrides to be used when rendering the fragment.
QgsTextLabelFeature(QgsFeatureId id, geos::unique_ptr geometry, QSizeF size)
Construct text label feature.
static QgsPrecalculatedTextMetrics calculateTextMetrics(const QgsMapToPixel *xform, const QgsRenderContext &context, const QgsTextFormat &format, const QFont &baseFont, const QFontMetricsF &fontMetrics, double letterSpacing, double wordSpacing, const QString &text=QString(), QgsTextDocument *document=nullptr, QgsTextDocumentMetrics *metrics=nullptr)
Calculate text metrics for later retrieval via textMetrics().
~QgsTextLabelFeature() override
Clean up.
const QgsTextDocument & document() const
Returns the document for the label.
QgsTextCharacterFormat characterFormat(int partId) const
Returns the character format corresponding to the specified label part.
QFont mDefinedFont
Font for rendering.
QgsTextDocumentMetrics mDocumentMetrics
QString text(int partId) const
Returns the text component corresponding to a specified label part.
bool hasCharacterFormat(int partId) const
Returns true if the feature contains specific character formatting for the part with matching ID.
std::optional< QgsPrecalculatedTextMetrics > mTextMetrics
void setDocument(const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics)
Sets the document and document metrics for the label.
static constexpr double SUPERSCRIPT_SUBSCRIPT_FONT_SIZE_SCALING_FACTOR
Scale factor to use for super or subscript text which doesn't have an explicit font size set.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6066
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features