QGIS API Documentation 3.99.0-Master (8e76e220402)
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
56QgsPrecalculatedTextMetrics QgsTextLabelFeature::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 & )
57{
58 const double tabStopDistancePainterUnits = format.tabStopDistanceUnit() == Qgis::RenderUnit::Percentage
59 ? format.tabStopDistance() * baseFont.pixelSize()
61
62 const QList< QgsTextFormat::Tab > tabPositions = format.tabPositions();
63 QList< double > tabStopDistancesPainterUnits;
64 tabStopDistancesPainterUnits.reserve( tabPositions.size() );
65 for ( const QgsTextFormat::Tab &tab : tabPositions )
66 {
67 tabStopDistancesPainterUnits.append(
69 ? tab.position() * baseFont.pixelSize()
70 : context.convertToPainterUnits( tab.position(), format.tabStopDistanceUnit(), format.tabStopDistanceMapUnitScale() )
71 );
72 }
73
74
75 // create label info!
76 const double mapScale = xform->mapUnitsPerPixel();
77 QStringList graphemes;
78 QVector< QgsTextCharacterFormat > graphemeFormats;
79
80 for ( const QgsTextBlock &block : std::as_const( document ) )
81 {
82 for ( const QgsTextFragment &fragment : block )
83 {
84 const QStringList fragmentGraphemes = QgsPalLabeling::splitToGraphemes( fragment.text() );
85 for ( const QString &grapheme : fragmentGraphemes )
86 {
87 graphemes.append( grapheme );
88 graphemeFormats.append( fragment.characterFormat() );
89 }
90 }
91 }
92
93 QVector< double > characterWidths( graphemes.count() );
94 QVector< double > characterHeights( graphemes.count() );
95 QVector< double > characterDescents( graphemes.count() );
96
97 QFont previousNonSuperSubScriptFont;
98
99 double currentWidth = 0;
100 for ( int i = 0; i < graphemes.count(); i++ )
101 {
102 // reconstruct how Qt creates word spacing, then adjust per individual stored character
103 // this will allow PAL to create each candidate width = character width + correct spacing
104
105 double graphemeFirstCharHorizontalAdvanceWithLetterSpacing = 0;
106 double graphemeFirstCharHorizontalAdvance = 0;
107 double graphemeHorizontalAdvance = 0;
108 double characterDescent = 0;
109 double characterHeight = 0;
110 if ( graphemes[i] == '\t' )
111 {
112 double nextTabStop = 0;
113 if ( !tabStopDistancesPainterUnits.empty() )
114 {
115 // if we don't find a tab stop before the current length of line, we just ignore the tab character entirely
116 nextTabStop = currentWidth;
117 for ( const double tabStop : std::as_const( tabStopDistancesPainterUnits ) )
118 {
119 if ( tabStop >= currentWidth )
120 {
121 nextTabStop = tabStop;
122 break;
123 }
124 }
125 }
126 else
127 {
128 nextTabStop = ( std::floor( currentWidth / tabStopDistancePainterUnits ) + 1 ) * tabStopDistancePainterUnits;
129 }
130
131 const double thisTabWidth = nextTabStop - currentWidth;
132
133 graphemeFirstCharHorizontalAdvance = thisTabWidth;
134 graphemeFirstCharHorizontalAdvanceWithLetterSpacing = thisTabWidth;
135 graphemeHorizontalAdvance = thisTabWidth;
136 characterDescent = fontMetrics.descent();
137 characterHeight = fontMetrics.height();
138 }
139 else if ( const QgsTextCharacterFormat *graphemeFormat = !graphemeFormats.empty() ? &graphemeFormats[i] : nullptr )
140 {
141 QFont graphemeFont = baseFont;
142 graphemeFormat->updateFontForFormat( graphemeFont, context, 1 );
143
144 if ( i == 0 )
145 previousNonSuperSubScriptFont = graphemeFont;
146
147 if ( graphemeFormat->hasVerticalAlignmentSet() )
148 {
149 switch ( graphemeFormat->verticalAlignment() )
150 {
152 previousNonSuperSubScriptFont = graphemeFont;
153 break;
154
157 {
158 if ( graphemeFormat->fontPointSize() < 0 )
159 {
160 // if fragment has no explicit font size set, then we scale the inherited font size to 60% of base font size
161 // this allows for easier use of super/subscript in labels as "my text<sup>2</sup>" will automatically render
162 // the superscript in a smaller font size. BUT if the fragment format HAS a non -1 font size then it indicates
163 // that the document has an explicit font size for the super/subscript element, eg "my text<sup style="font-size: 6pt">2</sup>"
164 // which we should respect
165 graphemeFont.setPixelSize( static_cast< int >( std::round( graphemeFont.pixelSize() * QgsTextRenderer::SUPERSCRIPT_SUBSCRIPT_FONT_SIZE_SCALING_FACTOR ) ) );
166 }
167 break;
168 }
169 }
170 }
171 else
172 {
173 previousNonSuperSubScriptFont = graphemeFont;
174 }
175
176 const QFontMetricsF graphemeFontMetrics( graphemeFont );
177 graphemeFirstCharHorizontalAdvance = graphemeFontMetrics.horizontalAdvance( QString( graphemes[i].at( 0 ) ) );
178 graphemeFirstCharHorizontalAdvanceWithLetterSpacing = graphemeFontMetrics.horizontalAdvance( graphemes[i].at( 0 ) ) + letterSpacing;
179 graphemeHorizontalAdvance = graphemeFontMetrics.horizontalAdvance( QString( graphemes[i] ) );
180 characterDescent = graphemeFontMetrics.descent();
181 characterHeight = graphemeFontMetrics.height();
182 }
183 else
184 {
185 graphemeFirstCharHorizontalAdvance = fontMetrics.horizontalAdvance( QString( graphemes[i].at( 0 ) ) );
186 graphemeFirstCharHorizontalAdvanceWithLetterSpacing = fontMetrics.horizontalAdvance( graphemes[i].at( 0 ) ) + letterSpacing;
187 graphemeHorizontalAdvance = fontMetrics.horizontalAdvance( QString( graphemes[i] ) );
188 characterDescent = fontMetrics.descent();
189 characterHeight = fontMetrics.height();
190 }
191
192 qreal wordSpaceFix = qreal( 0.0 );
193 if ( graphemes[i] == " "_L1 )
194 {
195 // word spacing only gets added once at end of consecutive run of spaces, see QTextEngine::shapeText()
196 int nxt = i + 1;
197 wordSpaceFix = ( nxt < graphemes.count() && graphemes[nxt] != " "_L1 ) ? wordSpacing : qreal( 0.0 );
198 }
199
200 // this workaround only works for clusters with a single character. Not sure how it should be handled
201 // with multi-character clusters.
202 if ( graphemes[i].length() == 1 &&
203 !qgsDoubleNear( graphemeFirstCharHorizontalAdvance, graphemeFirstCharHorizontalAdvanceWithLetterSpacing ) )
204 {
205 // word spacing applied when it shouldn't be
206 wordSpaceFix -= wordSpacing;
207 }
208
209 const double charWidth = graphemeHorizontalAdvance + wordSpaceFix;
210 characterWidths[i] = mapScale * charWidth;
211 characterHeights[i] = mapScale * characterHeight;
212 characterDescents[i] = mapScale * characterDescent;
213
214 currentWidth += charWidth;
215 }
216
217 QgsPrecalculatedTextMetrics res( graphemes, std::move( characterWidths ), std::move( characterHeights ), std::move( characterDescents ) );
218 res.setGraphemeFormats( graphemeFormats );
219 return res;
220}
221
@ Normal
Adjacent characters are positioned in the standard way for text in the writing system in use.
Definition qgis.h:3036
@ SubScript
Characters are placed below the base line for normal text.
Definition qgis.h:3038
@ SuperScript
Characters are placed above the base line for normal text.
Definition qgis.h:3037
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5294
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:114
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6935
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features