QGIS API Documentation 4.1.0-Master (60fea48833c)
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 "qgsmaptopixel.h"
19#include "qgspallabeling.h"
20#include "qgsrendercontext.h"
21#include "qgstextblock.h"
23#include "qgstextfragment.h"
24#include "qgstextrenderer.h"
25
26#include <QString>
27
28using namespace Qt::StringLiterals;
29
35
37
38QString QgsTextLabelFeature::text( int partId ) const
39{
40 if ( partId == -1 )
41 return mLabelText;
42 else
43 return mTextMetrics->grapheme( partId );
44}
45
47{
48 return mTextMetrics.has_value() ? mTextMetrics->graphemeFormat( partId ) : QgsTextCharacterFormat();
49}
50
52{
53 return mTextMetrics.has_value() && partId < mTextMetrics->graphemeFormatCount();
54}
55
57 const QgsMapToPixel *xform,
58 const QgsRenderContext &context,
59 const QgsTextFormat &format,
60 const QFont &baseFont,
61 const QFontMetricsF &fontMetrics,
62 double letterSpacing,
63 double wordSpacing,
66)
67{
68 const double tabStopDistancePainterUnits = format.tabStopDistanceUnit() == Qgis::RenderUnit::Percentage
69 ? format.tabStopDistance() * baseFont.pixelSize()
71
72 const QList< QgsTextFormat::Tab > tabPositions = format.tabPositions();
73 QList< double > tabStopDistancesPainterUnits;
74 tabStopDistancesPainterUnits.reserve( tabPositions.size() );
75 for ( const QgsTextFormat::Tab &tab : tabPositions )
76 {
77 tabStopDistancesPainterUnits.append(
78 format.tabStopDistanceUnit() == Qgis::RenderUnit::Percentage ? tab.position() * baseFont.pixelSize()
79 : context.convertToPainterUnits( tab.position(), format.tabStopDistanceUnit(), format.tabStopDistanceMapUnitScale() )
80 );
81 }
82
83
84 // create label info!
85 const double mapScale = xform->mapUnitsPerPixel();
86 QStringList graphemes;
87 QVector< QgsTextCharacterFormat > graphemeFormats;
88
89 for ( const QgsTextBlock &block : std::as_const( document ) )
90 {
91 for ( const QgsTextFragment &fragment : block )
92 {
93 const QStringList fragmentGraphemes = QgsPalLabeling::splitToGraphemes( fragment.text() );
94 for ( const QString &grapheme : fragmentGraphemes )
95 {
96 graphemes.append( grapheme );
97 graphemeFormats.append( fragment.characterFormat() );
98 }
99 }
100 }
101
102 QVector< double > characterWidths( graphemes.count() );
103 QVector< double > characterHeights( graphemes.count() );
104 QVector< double > characterDescents( graphemes.count() );
105
106 QFont previousNonSuperSubScriptFont;
107
108 double currentWidth = 0;
109 for ( int i = 0; i < graphemes.count(); i++ )
110 {
111 // reconstruct how Qt creates word spacing, then adjust per individual stored character
112 // this will allow PAL to create each candidate width = character width + correct spacing
113
114 double graphemeFirstCharHorizontalAdvanceWithLetterSpacing = 0;
115 double graphemeFirstCharHorizontalAdvance = 0;
116 double graphemeHorizontalAdvance = 0;
117 double characterDescent = 0;
118 double characterHeight = 0;
119 if ( graphemes[i] == '\t' )
120 {
121 double nextTabStop = 0;
122 if ( !tabStopDistancesPainterUnits.empty() )
123 {
124 // if we don't find a tab stop before the current length of line, we just ignore the tab character entirely
125 nextTabStop = currentWidth;
126 for ( const double tabStop : std::as_const( tabStopDistancesPainterUnits ) )
127 {
128 if ( tabStop >= currentWidth )
129 {
130 nextTabStop = tabStop;
131 break;
132 }
133 }
134 }
135 else
136 {
137 nextTabStop = ( std::floor( currentWidth / tabStopDistancePainterUnits ) + 1 ) * tabStopDistancePainterUnits;
138 }
139
140 const double thisTabWidth = nextTabStop - currentWidth;
141
142 graphemeFirstCharHorizontalAdvance = thisTabWidth;
143 graphemeFirstCharHorizontalAdvanceWithLetterSpacing = thisTabWidth;
144 graphemeHorizontalAdvance = thisTabWidth;
145 characterDescent = fontMetrics.descent();
146 characterHeight = fontMetrics.height();
147 }
148 else if ( const QgsTextCharacterFormat *graphemeFormat = !graphemeFormats.empty() ? &graphemeFormats[i] : nullptr )
149 {
150 QFont graphemeFont = baseFont;
151 graphemeFormat->updateFontForFormat( graphemeFont, context, 1 );
152
153 if ( i == 0 )
154 previousNonSuperSubScriptFont = graphemeFont;
155
156 if ( graphemeFormat->hasVerticalAlignmentSet() )
157 {
158 switch ( graphemeFormat->verticalAlignment() )
159 {
161 previousNonSuperSubScriptFont = graphemeFont;
162 break;
163
166 {
167 if ( graphemeFormat->fontPointSize() < 0 )
168 {
169 // if fragment has no explicit font size set, then we scale the inherited font size to 60% of base font size
170 // this allows for easier use of super/subscript in labels as "my text<sup>2</sup>" will automatically render
171 // the superscript in a smaller font size. BUT if the fragment format HAS a non -1 font size then it indicates
172 // that the document has an explicit font size for the super/subscript element, eg "my text<sup style="font-size: 6pt">2</sup>"
173 // which we should respect
174 graphemeFont.setPixelSize( static_cast< int >( std::round( graphemeFont.pixelSize() * QgsTextRenderer::SUPERSCRIPT_SUBSCRIPT_FONT_SIZE_SCALING_FACTOR ) ) );
175 }
176 break;
177 }
178 }
179 }
180 else
181 {
182 previousNonSuperSubScriptFont = graphemeFont;
183 }
184
185 const QFontMetricsF graphemeFontMetrics( graphemeFont );
186 graphemeFirstCharHorizontalAdvance = graphemeFontMetrics.horizontalAdvance( QString( graphemes[i].at( 0 ) ) );
187 graphemeFirstCharHorizontalAdvanceWithLetterSpacing = graphemeFontMetrics.horizontalAdvance( graphemes[i].at( 0 ) ) + letterSpacing;
188 graphemeHorizontalAdvance = graphemeFontMetrics.horizontalAdvance( QString( graphemes[i] ) );
189 characterDescent = graphemeFontMetrics.descent();
190 characterHeight = graphemeFontMetrics.height();
191 }
192 else
193 {
194 graphemeFirstCharHorizontalAdvance = fontMetrics.horizontalAdvance( QString( graphemes[i].at( 0 ) ) );
195 graphemeFirstCharHorizontalAdvanceWithLetterSpacing = fontMetrics.horizontalAdvance( graphemes[i].at( 0 ) ) + letterSpacing;
196 graphemeHorizontalAdvance = fontMetrics.horizontalAdvance( QString( graphemes[i] ) );
197 characterDescent = fontMetrics.descent();
198 characterHeight = fontMetrics.height();
199 }
200
201 qreal wordSpaceFix = qreal( 0.0 );
202 if ( graphemes[i] == " "_L1 )
203 {
204 // word spacing only gets added once at end of consecutive run of spaces, see QTextEngine::shapeText()
205 int nxt = i + 1;
206 wordSpaceFix = ( nxt < graphemes.count() && graphemes[nxt] != " "_L1 ) ? wordSpacing : qreal( 0.0 );
207 }
208
209 // this workaround only works for clusters with a single character. Not sure how it should be handled
210 // with multi-character clusters.
211 if ( graphemes[i].length() == 1 && !qgsDoubleNear( graphemeFirstCharHorizontalAdvance, graphemeFirstCharHorizontalAdvanceWithLetterSpacing ) )
212 {
213 // word spacing applied when it shouldn't be
214 wordSpaceFix -= wordSpacing;
215 }
216
217 const double charWidth = graphemeHorizontalAdvance + wordSpaceFix;
218 characterWidths[i] = mapScale * charWidth;
219 characterHeights[i] = mapScale * characterHeight;
220 characterDescents[i] = mapScale * characterDescent;
221
222 currentWidth += charWidth;
223 }
224
225 QgsPrecalculatedTextMetrics res( graphemes, std::move( characterWidths ), std::move( characterHeights ), std::move( characterDescents ) );
226 res.setGraphemeFormats( graphemeFormats );
227 return res;
228}
229
@ Normal
Adjacent characters are positioned in the standard way for text in the writing system in use.
Definition qgis.h:3079
@ SubScript
Characters are placed below the base line for normal text.
Definition qgis.h:3081
@ SuperScript
Characters are placed above the base line for normal text.
Definition qgis.h:3080
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5344
QSizeF size(double angle=0.0) const
Size of the label (in map units).
GEOSGeometry * geometry() const
Gets access to the associated geometry.
QgsLabelFeature(QgsFeatureId id, geos::unique_ptr geometry, QSizeF size, int subPartId=0)
Constructor for QgsLabelFeature.
QgsFeatureId id() const
Identifier of the label (unique within the parent label provider).
QString mLabelText
text of the label
int subPartId() const
Sub part identifier (for features which register multiple labels).
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 rendered 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() 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
QgsTextLabelFeature(QgsFeatureId id, geos::unique_ptr geometry, QSizeF size, int subPartId=0)
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 QgsTextDocument &document, const QgsTextDocumentMetrics &metrics)
Calculate text metrics for later retrieval via textMetrics().
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.
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:112
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6975
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features