QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 "qgsgeometry.h"
19 #include "qgspallabeling.h"
20 #include "qgsmaptopixel.h"
21 #include "pal/feature.h"
22 #include "qgstextcharacterformat.h"
23 #include "qgstextfragment.h"
24 #include "qgstextblock.h"
25 
27  : QgsLabelFeature( id, std::move( geometry ), size )
28 {
29  mDefinedFont = QFont();
30 }
31 
32 
34 {
35  delete mFontMetrics;
36 }
37 
38 
39 QString QgsTextLabelFeature::text( int partId ) const
40 {
41  if ( partId == -1 )
42  return mLabelText;
43  else
44  return mClusters.at( partId );
45 }
46 
48 {
49  return mCharacterFormats.value( partId );
50 }
51 
53 {
54  return partId < mCharacterFormats.size();
55 }
56 
57 void QgsTextLabelFeature::calculateInfo( bool curvedLabeling, QFontMetricsF *fm, const QgsMapToPixel *xform, double maxinangle, double maxoutangle, QgsTextDocument *document )
58 {
59  if ( mInfo )
60  return;
61 
62  mFontMetrics = new QFontMetricsF( *fm ); // duplicate metrics for when drawing label
63 
64  qreal letterSpacing = mDefinedFont.letterSpacing();
65  qreal wordSpacing = mDefinedFont.wordSpacing();
66 
67  // max angle between curved label characters (20.0/-20.0 was default in QGIS <= 1.8)
68  if ( maxinangle < 20.0 )
69  maxinangle = 20.0;
70  if ( 60.0 < maxinangle )
71  maxinangle = 60.0;
72  if ( maxoutangle > -20.0 )
73  maxoutangle = -20.0;
74  if ( -95.0 > maxoutangle )
75  maxoutangle = -95.0;
76 
77  // create label info!
78  double mapScale = xform->mapUnitsPerPixel();
79  double labelHeight = mapScale * fm->height();
80 
81  // mLetterSpacing/mWordSpacing = 0.0 is default for non-curved labels
82  // (non-curved spacings handled by Qt in QgsPalLayerSettings/QgsPalLabeling)
83  qreal charWidth;
84  qreal wordSpaceFix;
85 
86  if ( document && curvedLabeling )
87  {
88  for ( const QgsTextBlock &block : qgis::as_const( *document ) )
89  {
90  for ( const QgsTextFragment &fragment : block )
91  {
92  const QStringList graphemes = QgsPalLabeling::splitToGraphemes( fragment.text() );
93  for ( const QString &grapheme : graphemes )
94  {
95  mClusters.append( grapheme );
96  mCharacterFormats.append( fragment.characterFormat() );
97  }
98  }
99  }
100  }
101  else
102  {
103  //split string by valid grapheme boundaries - required for certain scripts (see #6883)
105  }
106 
107  mInfo = new pal::LabelInfo( mClusters.count(), labelHeight, maxinangle, maxoutangle );
108  for ( int i = 0; i < mClusters.count(); i++ )
109  {
110  // reconstruct how Qt creates word spacing, then adjust per individual stored character
111  // this will allow PAL to create each candidate width = character width + correct spacing
112 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
113  charWidth = fm->width( mClusters[i] );
114 #else
115  charWidth = fm->horizontalAdvance( mClusters[i] );
116 #endif
117  if ( curvedLabeling )
118  {
119  wordSpaceFix = qreal( 0.0 );
120  if ( mClusters[i] == QLatin1String( " " ) )
121  {
122  // word spacing only gets added once at end of consecutive run of spaces, see QTextEngine::shapeText()
123  int nxt = i + 1;
124  wordSpaceFix = ( nxt < mClusters.count() && mClusters[nxt] != QLatin1String( " " ) ) ? wordSpacing : qreal( 0.0 );
125  }
126  // this workaround only works for clusters with a single character. Not sure how it should be handled
127  // with multi-character clusters.
128 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
129  if ( mClusters[i].length() == 1 &&
130  !qgsDoubleNear( fm->width( QString( mClusters[i].at( 0 ) ) ), fm->width( mClusters[i].at( 0 ) ) + letterSpacing ) )
131 #else
132  if ( mClusters[i].length() == 1 &&
133  !qgsDoubleNear( fm->horizontalAdvance( QString( mClusters[i].at( 0 ) ) ), fm->horizontalAdvance( mClusters[i].at( 0 ) ) + letterSpacing ) )
134 #endif
135  {
136  // word spacing applied when it shouldn't be
137  wordSpaceFix -= wordSpacing;
138  }
139 
140 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
141  charWidth = fm->width( QString( mClusters[i] ) ) + wordSpaceFix;
142 #else
143  charWidth = fm->horizontalAdvance( QString( mClusters[i] ) ) + wordSpaceFix;
144 #endif
145  }
146 
147  double labelWidth = mapScale * charWidth;
148  mInfo->char_info[i].width = labelWidth;
149  }
150 }
151 
153 {
154  return mDocument;
155 }
156 
158 {
160 }
The QgsLabelFeature class describes a feature that should be used within the labeling engine.
pal::LabelInfo * mInfo
extra information for curved labels (may be nullptr)
QString mLabelText
text of the label
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
double mapUnitsPerPixel() const
Returns 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 ...
Represents a block of text consisting of one or more QgsTextFragment objects.
Definition: qgstextblock.h:36
Stores information relating to individual character formatting.
Represents a document consisting of one or more QgsTextBlock objects.
Stores a fragment of text along with formatting overrides to be used when rendering the fragment.
QgsTextLabelFeature(QgsFeatureId id, GEOSGeometry *geometry, const QSizeF &size)
Construct text label feature.
void setDocument(const QgsTextDocument &document)
Sets the document for the label.
QList< QgsTextCharacterFormat > mCharacterFormats
QStringList mClusters
List of graphemes (used for curved labels)
QgsTextDocument document() const
Returns the document for the label.
QFontMetricsF * mFontMetrics
Metrics of the font for rendering.
QgsTextDocument mDocument
QgsTextCharacterFormat characterFormat(int partId) const
Returns the character format corresponding to the specified label part.
QFont mDefinedFont
Font for rendering.
void calculateInfo(bool curvedLabeling, QFontMetricsF *fm, const QgsMapToPixel *xform, double fontScale, double maxinangle, double maxoutangle)
calculate data for info(). setDefinedFont() must have been called already.
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.
~QgsTextLabelFeature()
Clean up.
Optional additional info about label (for curved labels)
Definition: feature.h:56
CharacterInfo * char_info
Definition: feature.h:83
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition: qgsgeos.h:79
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28