QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgsscalebarrenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsscalebarrenderer.cpp
3  -----------------------
4  begin : June 2008
5  copyright : (C) 2008 by Marco Hugentobler
6  email : [email protected]
7  ***************************************************************************/
8 /***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
17 #include "qgsscalebarrenderer.h"
18 #include "qgsscalebarsettings.h"
19 #include "qgslayoututils.h"
20 #include "qgstextrenderer.h"
22 #include "qgsnumericformat.h"
23 #include "qgssymbol.h"
24 #include "qgssymbollayerutils.h"
25 #include <QFontMetricsF>
26 #include <QPainter>
27 
28 void QgsScaleBarRenderer::drawDefaultLabels( QgsRenderContext &context, const QgsScaleBarSettings &settings, const ScaleBarContext &scaleContext ) const
29 {
30  if ( !context.painter() )
31  {
32  return;
33  }
34 
35  QPainter *painter = context.painter();
36 
37  painter->save();
38 
39  QgsTextFormat format = settings.textFormat();
40 
41  QgsExpressionContextScope *scaleScope = new QgsExpressionContextScope( QStringLiteral( "scalebar_text" ) );
42  QgsExpressionContextScopePopper scopePopper( context.expressionContext(), scaleScope );
43 
44  QString firstLabel = firstLabelString( settings );
45  QFontMetricsF fontMetrics = QgsTextRenderer::fontMetrics( context, format );
46 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
47  double xOffset = fontMetrics.width( firstLabel ) / 2.0;
48 #else
49  double xOffset = fontMetrics.horizontalAdvance( firstLabel ) / 2.0;
50 #endif
51 
52  double scaledBoxContentSpace = context.convertToPainterUnits( settings.boxContentSpace(), QgsUnitTypes::RenderMillimeters );
53  double scaledLabelBarSpace = context.convertToPainterUnits( settings.labelBarSpace(), QgsUnitTypes::RenderMillimeters );
54  double scaledHeight;
55  if ( ( scaleContext.flags & Flag::FlagUsesSubdivisionsHeight ) && ( settings.numberOfSubdivisions() > 1 ) && ( settings.subdivisionsHeight() > settings.height() ) )
56  {
57  scaledHeight = context.convertToPainterUnits( settings.subdivisionsHeight(), QgsUnitTypes::RenderMillimeters );
58  }
59  else
60  {
61  scaledHeight = context.convertToPainterUnits( settings.height(), QgsUnitTypes::RenderMillimeters );
62  }
63 
64  double currentLabelNumber = 0.0;
65 
66  int nSegmentsLeft = settings.numberOfSegmentsLeft();
67  int segmentCounter = 0;
68 
69  QString currentNumericLabel;
70  QList<double> positions = segmentPositions( context, scaleContext, settings );
71 
72  bool drawZero = true;
73  switch ( settings.labelHorizontalPlacement() )
74  {
76  drawZero = false;
77  break;
79  drawZero = true;
80  break;
81  }
82 
83  QgsNumericFormatContext numericContext;
84 
85  for ( int i = 0; i < positions.size(); ++i )
86  {
87  if ( segmentCounter == 0 && nSegmentsLeft > 0 )
88  {
89  //label first left segment
90  currentNumericLabel = firstLabel;
91  }
92  else if ( segmentCounter != 0 && segmentCounter == nSegmentsLeft ) //reset label number to 0 if there are left segments
93  {
94  currentLabelNumber = 0.0;
95  }
96 
97  if ( segmentCounter >= nSegmentsLeft )
98  {
99  currentNumericLabel = settings.numericFormat()->formatDouble( currentLabelNumber / settings.mapUnitsPerScaleBarUnit(), numericContext );
100  }
101 
102  //don't draw label for intermediate left segments or the zero label when it needs to be skipped
103  if ( ( segmentCounter == 0 || segmentCounter >= nSegmentsLeft ) && ( currentNumericLabel != QLatin1String( "0" ) || drawZero ) )
104  {
105  scaleScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "scale_value" ), currentNumericLabel, true, false ) );
106  QPointF pos;
108  {
109  if ( segmentCounter == 0 )
110  {
111  // if the segment counter is zero with a non zero label, this is the left-of-zero label
112  pos.setX( context.convertToPainterUnits( positions.at( i ) + ( scaleContext.segmentWidth / 2 ), QgsUnitTypes::RenderMillimeters ) );
113  }
114  else
115  {
116  pos.setX( context.convertToPainterUnits( positions.at( i ) - ( scaleContext.segmentWidth / 2 ), QgsUnitTypes::RenderMillimeters ) );
117  }
118  }
119  else
120  {
121  pos.setX( context.convertToPainterUnits( positions.at( i ), QgsUnitTypes::RenderMillimeters ) + xOffset );
122  }
123  pos.setY( fontMetrics.ascent() + scaledBoxContentSpace + ( settings.labelVerticalPlacement() == QgsScaleBarSettings::LabelBelowSegment ? scaledHeight + scaledLabelBarSpace : 0 ) );
124  QgsTextRenderer::drawText( pos, 0, QgsTextRenderer::AlignCenter, QStringList() << currentNumericLabel, context, format );
125  }
126 
127  if ( segmentCounter >= nSegmentsLeft )
128  {
129  currentLabelNumber += settings.unitsPerSegment();
130  }
131  ++segmentCounter;
132  }
133 
134  //also draw the last label
135  if ( !positions.isEmpty() )
136  {
137  // note: this label is NOT centered over the end of the bar - rather the numeric portion
138  // of it is, without considering the unit label suffix. That's drawn at the end after
139  // horizontally centering just the numeric portion.
140  currentNumericLabel = settings.numericFormat()->formatDouble( currentLabelNumber / settings.mapUnitsPerScaleBarUnit(), numericContext );
141  scaleScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "scale_value" ), currentNumericLabel, true, false ) );
142  QPointF pos;
143  pos.setY( fontMetrics.ascent() + scaledBoxContentSpace + ( settings.labelVerticalPlacement() == QgsScaleBarSettings::LabelBelowSegment ? scaledHeight + scaledLabelBarSpace : 0 ) );
145  {
146  pos.setX( context.convertToPainterUnits( positions.at( positions.size() - 1 ) + ( scaleContext.segmentWidth / 2 ), QgsUnitTypes::RenderMillimeters ) + xOffset );
147  QgsTextRenderer::drawText( pos, 0, QgsTextRenderer::AlignCenter, QStringList() << ( currentNumericLabel + ' ' + settings.unitLabel() ), context, format );
148  }
149  else
150  {
151 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
152  pos.setX( context.convertToPainterUnits( positions.at( positions.size() - 1 ) + scaleContext.segmentWidth, QgsUnitTypes::RenderMillimeters ) + xOffset
153  - fontMetrics.width( currentNumericLabel ) / 2.0 );
154 #else
155  pos.setX( context.convertToPainterUnits( positions.at( positions.size() - 1 ) + scaleContext.segmentWidth, QgsUnitTypes::RenderMillimeters ) + xOffset
156  - fontMetrics.horizontalAdvance( currentNumericLabel ) / 2.0 );
157 #endif
158  QgsTextRenderer::drawText( pos, 0, QgsTextRenderer::AlignLeft, QStringList() << ( currentNumericLabel + ' ' + settings.unitLabel() ), context, format );
159  }
160  }
161 
162  painter->restore();
163 }
164 
165 QgsScaleBarRenderer::Flags QgsScaleBarRenderer::flags() const
166 {
167  return QgsScaleBarRenderer::Flags();
168 }
169 
171 {
172  return 100;
173 }
174 
176  const QgsScaleBarRenderer::ScaleBarContext &scaleContext ) const
177 {
178  QFont font = settings.textFormat().toQFont();
179 
180  //consider centered first label
181  double firstLabelWidth = QgsLayoutUtils::textWidthMM( font, firstLabelString( settings ) );
183  {
184  if ( firstLabelWidth > scaleContext.segmentWidth )
185  {
186  firstLabelWidth = ( firstLabelWidth - scaleContext.segmentWidth ) / 2;
187  }
188  else
189  {
190  firstLabelWidth = 0.0;
191  }
192  }
193  else
194  {
195  firstLabelWidth = firstLabelWidth / 2;
196  }
197 
198  //consider last number and label
199  double largestLabelNumber = settings.numberOfSegments() * settings.unitsPerSegment() / settings.mapUnitsPerScaleBarUnit();
200  QString largestNumberLabel = settings.numericFormat()->formatDouble( largestLabelNumber, QgsNumericFormatContext() );
201  QString largestLabel = largestNumberLabel + ' ' + settings.unitLabel();
202  double largestLabelWidth;
204  {
205  largestLabelWidth = QgsLayoutUtils::textWidthMM( font, largestLabel );
206  if ( largestLabelWidth > scaleContext.segmentWidth )
207  {
208  largestLabelWidth = ( largestLabelWidth - scaleContext.segmentWidth ) / 2;
209  }
210  else
211  {
212  largestLabelWidth = 0.0;
213  }
214  }
215  else
216  {
217  largestLabelWidth = QgsLayoutUtils::textWidthMM( font, largestLabel ) - QgsLayoutUtils::textWidthMM( font, largestNumberLabel ) / 2;
218  }
219 
220  double totalBarLength = scaleContext.segmentWidth * ( settings.numberOfSegments() + ( settings.numberOfSegmentsLeft() > 0 ? 1 : 0 ) );
221 
222  // this whole method is deprecated, so we can still call the deprecated settings.pen() getter
224  double width = firstLabelWidth + totalBarLength + 2 * settings.pen().widthF() + largestLabelWidth + 2 * settings.boxContentSpace();
226 
227  double height = settings.height() + settings.labelBarSpace() + 2 * settings.boxContentSpace() + QgsLayoutUtils::fontAscentMM( font );
228 
229  return QSizeF( width, height );
230 }
231 
233 {
234  const double painterToMm = 1.0 / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
235  //consider centered first label
236  double firstLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << firstLabelString( settings ) ) * painterToMm;
237 
239  {
240  if ( firstLabelWidth > scaleContext.segmentWidth )
241  {
242  firstLabelWidth = ( firstLabelWidth - scaleContext.segmentWidth ) / 2;
243  }
244  else
245  {
246  firstLabelWidth = 0.0;
247  }
248  }
249  else
250  {
251  firstLabelWidth = firstLabelWidth / 2;
252  }
253 
254  //consider last number and label
255  double largestLabelNumber = settings.numberOfSegments() * settings.unitsPerSegment() / settings.mapUnitsPerScaleBarUnit();
256  QString largestNumberLabel = settings.numericFormat()->formatDouble( largestLabelNumber, QgsNumericFormatContext() );
257  QString largestLabel = largestNumberLabel + ' ' + settings.unitLabel();
258  double largestLabelWidth;
260  {
261  largestLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm;
262 
263  if ( largestLabelWidth > scaleContext.segmentWidth )
264  {
265  largestLabelWidth = ( largestLabelWidth - scaleContext.segmentWidth ) / 2;
266  }
267  else
268  {
269  largestLabelWidth = 0.0;
270  }
271  }
272  else
273  {
274  largestLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm
275  - QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestNumberLabel ) * painterToMm / 2;
276  }
277 
278  double totalBarLength = scaleContext.segmentWidth * ( settings.numberOfSegments() + ( settings.numberOfSegmentsLeft() > 0 ? 1 : 0 ) );
279 
280  double lineWidth = QgsSymbolLayerUtils::estimateMaxSymbolBleed( settings.lineSymbol(), context ) * 2;
281  // need to convert to mm
282  lineWidth /= context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
283 
284  double width = firstLabelWidth + totalBarLength + 2 * lineWidth + largestLabelWidth + 2 * settings.boxContentSpace();
285  double height;
286  if ( ( scaleContext.flags & Flag::FlagUsesSubdivisionsHeight ) && ( settings.numberOfSubdivisions() > 1 ) && ( settings.subdivisionsHeight() > settings.height() ) )
287  {
288  height = settings.subdivisionsHeight();
289  }
290  else
291  {
292  height = settings.height();
293  }
294 
295  // TODO -- we technically should check the height of ALL labels here and take the maximum
296  height += settings.labelBarSpace() + 2 * settings.boxContentSpace() + QgsTextRenderer::textHeight( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm;
297 
298  return QSizeF( width, height );
299 }
300 
302 {
303  return false;
304 }
305 
307 {
308  if ( settings.numberOfSegmentsLeft() > 0 )
309  {
310  return settings.numericFormat()->formatDouble( settings.unitsPerSegment() / settings.mapUnitsPerScaleBarUnit(), QgsNumericFormatContext() );
311  }
312  else
313  {
314  return settings.numericFormat()->formatDouble( 0, QgsNumericFormatContext() );
315  }
316 }
317 
319 {
320  QString firstLabel = firstLabelString( settings );
322  return QgsLayoutUtils::textWidthMM( settings.font(), firstLabel ) / 2.0;
324 }
325 
326 double QgsScaleBarRenderer::firstLabelXOffset( const QgsScaleBarSettings &settings, const QgsRenderContext &context, const ScaleBarContext &scaleContext ) const
327 {
328  QString firstLabel = firstLabelString( settings );
329  double firstLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << firstLabel );
331  {
332  if ( firstLabelWidth > scaleContext.segmentWidth )
333  {
334  firstLabelWidth = ( firstLabelWidth - scaleContext.segmentWidth ) / 2;
335  }
336  else
337  {
338  firstLabelWidth = 0.0;
339  }
340  }
341  else
342  {
343  firstLabelWidth = firstLabelWidth / 2;
344  }
345  return firstLabelWidth;
346 }
347 
348 QList<double> QgsScaleBarRenderer::segmentPositions( const ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const
349 {
350  QList<double> positions;
351 
352  // this whole method is deprecated, so calling a deprecated function is fine
354  double currentXCoord = settings.pen().widthF() + settings.boxContentSpace();
356 
357  //left segments
358  double leftSegmentSize = scaleContext.segmentWidth / settings.numberOfSegmentsLeft();
359  positions.reserve( settings.numberOfSegmentsLeft() + settings.numberOfSegments() );
360  for ( int i = 0; i < settings.numberOfSegmentsLeft(); ++i )
361  {
362  positions << currentXCoord;
363  currentXCoord += leftSegmentSize;
364  }
365 
366  //right segments
367  for ( int i = 0; i < settings.numberOfSegments(); ++i )
368  {
369  positions << currentXCoord;
370  currentXCoord += scaleContext.segmentWidth;
371  }
372  return positions;
373 }
374 
375 QList<double> QgsScaleBarRenderer::segmentPositions( QgsRenderContext &context, const QgsScaleBarRenderer::ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const
376 {
377  QList<double> positions;
378 
379  double lineWidth = QgsSymbolLayerUtils::estimateMaxSymbolBleed( settings.lineSymbol(), context ) * 2.0;
380  // need to convert to mm
381  lineWidth /= context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
382 
383  double currentXCoord = lineWidth + settings.boxContentSpace();
384 
385  //left segments
386  double leftSegmentSize = scaleContext.segmentWidth / settings.numberOfSegmentsLeft();
387  positions.reserve( settings.numberOfSegmentsLeft() + settings.numberOfSegments() );
388  for ( int i = 0; i < settings.numberOfSegmentsLeft(); ++i )
389  {
390  positions << currentXCoord;
391  currentXCoord += leftSegmentSize;
392  }
393 
394  //right segments
395  for ( int i = 0; i < settings.numberOfSegments(); ++i )
396  {
397  positions << currentXCoord;
398  currentXCoord += scaleContext.segmentWidth;
399  }
400  return positions;
401 }
402 
403 QList<double> QgsScaleBarRenderer::segmentWidths( const ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const
404 {
405  QList<double> widths;
406  widths.reserve( settings.numberOfSegmentsLeft() + settings.numberOfSegments() );
407 
408  //left segments
409  if ( settings.numberOfSegmentsLeft() > 0 )
410  {
411  double leftSegmentSize = scaleContext.segmentWidth / settings.numberOfSegmentsLeft();
412  for ( int i = 0; i < settings.numberOfSegmentsLeft(); ++i )
413  {
414  widths << leftSegmentSize;
415  }
416  }
417 
418  //right segments
419  for ( int i = 0; i < settings.numberOfSegments(); ++i )
420  {
421  widths << scaleContext.segmentWidth;
422  }
423 
424  return widths;
425 }
RAII class to pop scope from an expression context on destruction.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
static double fontAscentMM(const QFont &font)
Calculates a font ascent in millimeters, including workarounds for QT font rendering issues.
static double textWidthMM(const QFont &font, const QString &text)
Calculate a font width in millimeters for a text string, including workarounds for QT font rendering ...
A context for numeric formats.
virtual QString formatDouble(double value, const QgsNumericFormatContext &context) const =0
Returns a formatted string representation of a numeric double value.
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
@ FlagUsesSubdivisionsHeight
Renderer uses the scalebar subdivisions height (see QgsScaleBarSettings::subdivisionsHeight() )
virtual Flags flags() const
Returns the scalebar rendering flags, which dictates the renderer's behavior.
virtual bool applyDefaultSettings(QgsScaleBarSettings &settings) const
Applies any default settings relating to the scalebar to the passed settings object.
virtual int sortKey() const
Returns a sorting key value, where renderers with a lower sort key will be shown earlier in lists.
void drawDefaultLabels(QgsRenderContext &context, const QgsScaleBarSettings &settings, const QgsScaleBarRenderer::ScaleBarContext &scaleContext) const
Draws default scalebar labels using the specified settings and scaleContext to a destination render c...
virtual Q_DECL_DEPRECATED QSizeF calculateBoxSize(const QgsScaleBarSettings &settings, const QgsScaleBarRenderer::ScaleBarContext &scaleContext) const
Calculates the required box size (in millimeters) for a scalebar using the specified settings and sca...
QString firstLabelString(const QgsScaleBarSettings &settings) const
Returns the text used for the first label in the scalebar.
QList< double > segmentWidths(const QgsScaleBarRenderer::ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings) const
Returns a list of widths of each segment of the scalebar.
Q_DECL_DEPRECATED QList< double > segmentPositions(const QgsScaleBarRenderer::ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings) const
Returns a list of positions for each segment within the scalebar.
Q_DECL_DEPRECATED double firstLabelXOffset(const QgsScaleBarSettings &settings) const
Returns the x-offset (in millimeters) used for the first label in the scalebar.
The QgsScaleBarSettings class stores the appearance and layout settings for scalebar drawing with Qgs...
double subdivisionsHeight() const
Returns the scalebar subdivisions height (in millimeters) for segments included in the right part of ...
QgsLineSymbol * lineSymbol() const
Returns the line symbol used to render the scalebar (only used for some scalebar types).
const QgsNumericFormat * numericFormat() const
Returns the numeric format used for numbers in the scalebar.
int numberOfSegments() const
Returns the number of segments included in the scalebar.
double unitsPerSegment() const
Returns the number of scalebar units per segment.
Q_DECL_DEPRECATED QPen pen() const
Returns the pen used for drawing outlines in the scalebar.
LabelHorizontalPlacement labelHorizontalPlacement() const
Returns the horizontal placement of text labels.
double boxContentSpace() const
Returns the spacing (margin) between the scalebar box and content in millimeters.
LabelVerticalPlacement labelVerticalPlacement() const
Returns the vertical placement of text labels.
QString unitLabel() const
Returns the label for units.
int numberOfSubdivisions() const
Returns the number of subdivisions for segments included in the right part of the scalebar (only used...
Q_DECL_DEPRECATED QFont font() const
Returns the font used for drawing text in the scalebar.
@ LabelCenteredSegment
Labels are drawn centered relative to segment.
@ LabelCenteredEdge
Labels are drawn centered relative to segment's edge.
@ LabelBelowSegment
Labels are drawn below the scalebar.
double labelBarSpace() const
Returns the spacing (in millimeters) between labels and the scalebar.
QgsTextFormat & textFormat()
Returns the text format used for drawing text in the scalebar.
double height() const
Returns the scalebar height (in millimeters).
int numberOfSegmentsLeft() const
Returns the number of segments included in the left part of the scalebar.
double mapUnitsPerScaleBarUnit() const
Returns the number of map units per scale bar unit used by the scalebar.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
QFont toQFont() const
Returns a QFont matching the relevant settings from this text format.
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
@ AlignLeft
Left align.
@ AlignCenter
Center align.
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode=Point, QFontMetricsF *fontMetrics=nullptr)
Returns the height of a text based on a given format.
static QFontMetricsF fontMetrics(QgsRenderContext &context, const QgsTextFormat &format, double scaleFactor=1.0)
Returns the font metrics for the given text format, when rendered in the specified render context.
static void drawText(const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, VAlignment vAlignment=AlignTop)
Draws text within a rectangle using the specified settings.
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:168
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:798
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:797
Single variable definition for use within a QgsExpressionContextScope.
Contains parameters regarding scalebar calculations.
Flags flags
Scalebar renderer flags.
double segmentWidth
The width, in millimeters, of each individual segment drawn.