QGIS API Documentation 4.3.0-Master (16eddb2670e)
Loading...
Searching...
No Matches
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 : marco.hugentobler@karto.baug.ethz.ch
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
20#include "qgslayoututils.h"
21#include "qgslinesymbol.h"
22#include "qgsnumericformat.h"
23#include "qgspainting.h"
24#include "qgsscalebarsettings.h"
25#include "qgssymbol.h"
26#include "qgssymbollayerutils.h"
27#include "qgstextrenderer.h"
28
29#include <QFontMetricsF>
30#include <QPainter>
31#include <QString>
32
33using namespace Qt::StringLiterals;
34
35void QgsScaleBarRenderer::drawDefaultLabels( QgsRenderContext &context, const QgsScaleBarSettings &settings, const ScaleBarContext &scaleContext ) const
36{
37 if ( !context.painter() )
38 {
39 return;
40 }
41
42 QPainter *painter = context.painter();
43
44 QgsScopedQPainterState painterState( painter );
45
46 const QgsTextFormat format = settings.textFormat();
47
48 QgsExpressionContextScope *scaleScope = new QgsExpressionContextScope( u"scalebar_text"_s );
49 const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), scaleScope );
50
51 const QString firstLabel = firstLabelString( settings );
52 const QFontMetricsF fontMetrics = QgsTextRenderer::fontMetrics( context, format );
53 const double xOffset = firstLabelXOffset( settings, context, scaleContext );
54
55 const double scaledBoxContentSpace = context.convertToPainterUnits( settings.boxContentSpace(), Qgis::RenderUnit::Millimeters );
56 const double scaledLabelBarSpace = context.convertToPainterUnits( settings.labelBarSpace(), Qgis::RenderUnit::Millimeters );
57 double scaledHeight;
58 if ( ( scaleContext.flags & Flag::FlagUsesSubdivisionsHeight ) && ( settings.numberOfSubdivisions() > 1 ) && ( settings.subdivisionsHeight() > settings.height() ) )
59 {
61 }
62 else
63 {
64 scaledHeight = context.convertToPainterUnits( settings.height(), Qgis::RenderUnit::Millimeters );
65 }
66
67 const Qgis::ScaleBarUnitLabelPlacements unitPlacements = settings.unitLabelPlacements();
68 const QString unitLabel = settings.unitLabel();
69 const bool hasUnitLabel = !unitLabel.isEmpty();
70
71 const bool hasUnitLabelAbove
72 = hasUnitLabel
73 && ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightAbove ) );
74
75 const bool hasDistanceLabelsAbove = ( settings.labelVerticalPlacement() == Qgis::ScaleBarDistanceLabelVerticalPlacement::AboveSegment );
76 const bool hasDistanceLabelsBelow = ( settings.labelVerticalPlacement() == Qgis::ScaleBarDistanceLabelVerticalPlacement::BelowSegment );
77
78 double barTopPosition = scaledBoxContentSpace;
79 if ( hasUnitLabelAbove )
80 {
81 barTopPosition += fontMetrics.ascent() + scaledLabelBarSpace;
82 }
83 if ( hasDistanceLabelsAbove )
84 {
85 barTopPosition += fontMetrics.ascent() + scaledLabelBarSpace;
86 }
87
88 const double barBottomPosition = barTopPosition + scaledHeight;
89 const double barMiddleY = barTopPosition + scaledHeight / 2.0;
90
91 double distanceLabelY = 0.0;
92 if ( hasDistanceLabelsAbove )
93 {
94 distanceLabelY = barTopPosition - scaledLabelBarSpace;
95 }
96 else if ( hasDistanceLabelsBelow )
97 {
98 distanceLabelY = barBottomPosition + scaledLabelBarSpace + fontMetrics.ascent();
99 }
100
101 const QList<double> positions = segmentPositions( context, scaleContext, settings );
102 const int nSegmentsLeft = settings.numberOfSegmentsLeft();
103 const QgsNumericFormatContext numericContext;
104
105 switch ( settings.labelHorizontalPlacement() )
106 {
108 {
109 for ( int i = 0; i < positions.size(); ++i )
110 {
111 const bool isFirstLabel = ( i == 0 );
112 const bool isLastLabel = ( i == positions.size() - 1 );
113
114 QString currentNumericLabel;
115 if ( i < nSegmentsLeft )
116 {
117 if ( i == 0 )
118 {
119 currentNumericLabel = firstLabel;
120 }
121 else
122 {
123 // skip intermediate left segments
124 continue;
125 }
126 }
127 else
128 {
129 const double val = ( i - nSegmentsLeft + 1 ) * settings.unitsPerSegment();
130 currentNumericLabel = settings.numericFormat()->formatDouble( val / settings.mapUnitsPerScaleBarUnit(), numericContext );
131 }
132
133 const double segmentWidthMM = ( i < nSegmentsLeft && nSegmentsLeft > 0 ) ? ( scaleContext.segmentWidth / nSegmentsLeft ) : scaleContext.segmentWidth;
134
135 const QPointF pos( context.convertToPainterUnits( positions.at( i ) + ( segmentWidthMM / 2.0 ), Qgis::RenderUnit::Millimeters ) + xOffset, distanceLabelY );
136
137 QString labelText = currentNumericLabel;
138 if ( hasUnitLabel )
139 {
141 || ( isFirstLabel && unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeFirstDistanceLabel ) ) )
142 {
143 labelText = unitLabel + labelText;
144 }
146 || ( isLastLabel && unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::AfterLastDistanceLabel ) ) )
147 {
148 labelText = labelText + unitLabel;
149 }
150 }
151
152 scaleScope->addVariable( QgsExpressionContextScope::StaticVariable( u"scale_value"_s, currentNumericLabel, true, false ) );
153 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << labelText, context, format );
154 }
155 break;
156 }
157
159 {
160 double currentLabelNumber = 0.0;
161 int segmentCounter = 0;
162 QString currentNumericLabel;
163
164 for ( int i = 0; i < positions.size(); ++i )
165 {
166 if ( segmentCounter == 0 && nSegmentsLeft > 0 )
167 {
168 // label first left segment
169 currentNumericLabel = firstLabel;
170 }
171 else if ( segmentCounter != 0 && segmentCounter == nSegmentsLeft )
172 {
173 // reset label number to 0 if there are left segments
174 currentLabelNumber = 0.0;
175 }
176
177 if ( segmentCounter >= nSegmentsLeft )
178 {
179 currentNumericLabel = settings.numericFormat()->formatDouble( currentLabelNumber / settings.mapUnitsPerScaleBarUnit(), numericContext );
180 }
181
182 // don't draw label for intermediate left segments
183 if ( segmentCounter == 0 || segmentCounter >= nSegmentsLeft )
184 {
185 const bool isFirstLabel = ( segmentCounter == 0 );
186
187 scaleScope->addVariable( QgsExpressionContextScope::StaticVariable( u"scale_value"_s, currentNumericLabel, true, false ) );
188
189 QPointF pos( context.convertToPainterUnits( positions.at( i ), Qgis::RenderUnit::Millimeters ) + xOffset, distanceLabelY );
190
191 if ( isFirstLabel
192 && hasUnitLabel
195 {
196 pos.setX( pos.x() + fontMetrics.horizontalAdvance( currentNumericLabel ) / 2.0 );
197 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Right, QStringList() << ( unitLabel + currentNumericLabel ), context, format );
198 }
199 else
200 {
201 QString labelText = currentNumericLabel;
202 if ( hasUnitLabel )
203 {
204 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeEveryDistanceLabel ) )
205 {
206 labelText = unitLabel + labelText;
207 }
208 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::AfterEveryDistanceLabel ) )
209 {
210 labelText = labelText + unitLabel;
211 }
212 }
213 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << labelText, context, format );
214 }
215 }
216
217 if ( segmentCounter >= nSegmentsLeft )
218 {
219 currentLabelNumber += settings.unitsPerSegment();
220 }
221 ++segmentCounter;
222 }
223
224 // draw last label
225 if ( !positions.isEmpty() )
226 {
227 currentNumericLabel = settings.numericFormat()->formatDouble( currentLabelNumber / settings.mapUnitsPerScaleBarUnit(), numericContext );
228 QString lastLabelText = currentNumericLabel;
229
230 const bool hasSuffix
231 = hasUnitLabel
233 const bool hasPrefix = hasUnitLabel && unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeEveryDistanceLabel );
234 if ( hasPrefix )
235 {
236 lastLabelText = unitLabel + lastLabelText;
237 }
238 if ( hasSuffix )
239 {
240 lastLabelText = lastLabelText + unitLabel;
241 }
242
243 scaleScope->addVariable( QgsExpressionContextScope::StaticVariable( u"scale_value"_s, currentNumericLabel, true, false ) );
244 QPointF pos;
245 pos.setY( distanceLabelY );
246
247 if ( hasSuffix
248 && !hasPrefix
250 && !unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::AfterEveryDistanceLabel ) )
251 {
252 pos.setX(
253 context.convertToPainterUnits( positions.at( positions.size() - 1 ) + scaleContext.segmentWidth, Qgis::RenderUnit::Millimeters )
254 + xOffset
255 - fontMetrics.horizontalAdvance( currentNumericLabel ) / 2.0
256 );
257 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Left, QStringList() << ( currentNumericLabel + unitLabel ), context, format );
258 }
259 else
260 {
261 pos.setX( context.convertToPainterUnits( positions.at( positions.size() - 1 ) + scaleContext.segmentWidth, Qgis::RenderUnit::Millimeters ) + xOffset );
262 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << lastLabelText, context, format );
263 }
264 }
265 break;
266 }
267 }
268
269 // draw standalone unit labels
270 if ( hasUnitLabel && !positions.isEmpty() )
271 {
272 const double barLeftX = context.convertToPainterUnits( positions.first(), Qgis::RenderUnit::Millimeters ) + xOffset;
273 const double barRightX = context.convertToPainterUnits( positions.last() + scaleContext.segmentWidth, Qgis::RenderUnit::Millimeters ) + xOffset;
274
275 const double unitLabelYAbove = scaledBoxContentSpace + fontMetrics.ascent();
276
277 double unitLabelYBelow = barBottomPosition + scaledLabelBarSpace + fontMetrics.ascent();
278 if ( hasDistanceLabelsBelow )
279 {
280 unitLabelYBelow += fontMetrics.ascent() + scaledLabelBarSpace;
281 }
282
283 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeBar ) )
284 {
285 const QPointF pos( barLeftX - scaledLabelBarSpace, barMiddleY + ( fontMetrics.ascent() - fontMetrics.descent() ) / 2.0 );
286 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Right, QStringList() << unitLabel, context, format );
287 }
288 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::AfterBar ) )
289 {
290 const QPointF pos( barRightX + scaledLabelBarSpace, barMiddleY + ( fontMetrics.ascent() - fontMetrics.descent() ) / 2.0 );
291 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Left, QStringList() << unitLabel, context, format );
292 }
293 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftAbove ) )
294 {
295 const QPointF pos( barLeftX, unitLabelYAbove );
296 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
297 }
298 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredAbove ) )
299 {
300 const QPointF pos( ( barLeftX + barRightX ) / 2.0, unitLabelYAbove );
301 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
302 }
303 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightAbove ) )
304 {
305 const QPointF pos( barRightX, unitLabelYAbove );
306 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
307 }
308 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftBelow ) )
309 {
310 const QPointF pos( barLeftX, unitLabelYBelow );
311 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
312 }
313 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredBelow ) )
314 {
315 const QPointF pos( ( barLeftX + barRightX ) / 2.0, unitLabelYBelow );
316 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
317 }
318 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightBelow ) )
319 {
320 const QPointF pos( barRightX, unitLabelYBelow );
321 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
322 }
323 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::OnBarAfterFirstDivision ) )
324 {
325 const double firstDivCenterX = context.convertToPainterUnits( positions.value( nSegmentsLeft, positions.first() ) + scaleContext.segmentWidth / 2.0, Qgis::RenderUnit::Millimeters ) + xOffset;
326 const QPointF pos( firstDivCenterX, barMiddleY + ( fontMetrics.ascent() - fontMetrics.descent() ) / 2.0 );
327 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
328 }
329 }
330}
331
336
338{
339 return 100;
340}
341
343{
344 const QFont font = settings.textFormat().toQFont();
345
346 // consider centered first label
347 double firstLabelWidth = QgsLayoutUtils::textWidthMM( font, firstLabelString( settings ) );
349 {
350 if ( firstLabelWidth > scaleContext.segmentWidth )
351 {
352 firstLabelWidth = ( firstLabelWidth - scaleContext.segmentWidth ) / 2.0;
353 }
354 else
355 {
356 firstLabelWidth = 0.0;
357 }
358 }
359 else
360 {
361 firstLabelWidth /= 2.0;
362 }
363
364 // consider last number and label
365 const double largestLabelNumber = settings.numberOfSegments() * settings.unitsPerSegment() / settings.mapUnitsPerScaleBarUnit();
366 const QString largestNumberLabel = settings.numericFormat()->formatDouble( largestLabelNumber, QgsNumericFormatContext() );
367 const QString largestLabel = largestNumberLabel + settings.unitLabel();
368 double largestLabelWidth;
370 {
371 largestLabelWidth = QgsLayoutUtils::textWidthMM( font, largestLabel );
372 if ( largestLabelWidth > scaleContext.segmentWidth )
373 {
374 largestLabelWidth = ( largestLabelWidth - scaleContext.segmentWidth ) / 2.0;
375 }
376 else
377 {
378 largestLabelWidth = 0.0;
379 }
380 }
381 else
382 {
383 largestLabelWidth = QgsLayoutUtils::textWidthMM( font, largestLabel ) - QgsLayoutUtils::textWidthMM( font, largestNumberLabel ) / 2.0;
384 }
385
386 const double totalBarLength = scaleContext.segmentWidth * ( settings.numberOfSegments() + ( settings.numberOfSegmentsLeft() > 0 ? 1 : 0 ) );
387
388 // this whole method is deprecated, so we can still call the deprecated settings.pen() getter
390 const double width = firstLabelWidth + totalBarLength + 2.0 * settings.pen().widthF() + largestLabelWidth + 2.0 * settings.boxContentSpace();
392
393 const double height = settings.height() + settings.labelBarSpace() + 2.0 * settings.boxContentSpace() + QgsLayoutUtils::fontAscentMM( font );
394
395 return QSizeF( width, height );
396}
397
399{
400 const double painterToMm = 1.0 / context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
401 const Qgis::ScaleBarUnitLabelPlacements unitPlacements = settings.unitLabelPlacements();
402 const QString unitLabel = settings.unitLabel();
403 const bool hasUnitLabel = !unitLabel.isEmpty();
404
405 const QString firstNumLabel = ( settings.numberOfSegmentsLeft() > 0 )
408
409 const double firstSegmentWidth = ( settings.numberOfSegmentsLeft() > 0 ) ? ( scaleContext.segmentWidth / settings.numberOfSegmentsLeft() ) : scaleContext.segmentWidth;
410
411 double firstLabelWidth = 0.0;
412 if ( hasUnitLabel && unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeFirstDistanceLabel ) && !unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeEveryDistanceLabel ) )
413 {
414 const QString fullFirstLabel = unitLabel + firstNumLabel;
415 const double fullWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << fullFirstLabel ) * painterToMm;
416 const double numWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << firstNumLabel ) * painterToMm;
417
419 {
420 firstLabelWidth = ( fullWidth > firstSegmentWidth ) ? ( fullWidth - firstSegmentWidth ) / 2.0 : 0.0;
421 }
422 else
423 {
424 firstLabelWidth = fullWidth - numWidth / 2.0;
425 }
426 }
427 else
428 {
429 const QString fullFirstLabel = firstLabelString( settings );
430 const double fullWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << fullFirstLabel ) * painterToMm;
431
433 {
434 firstLabelWidth = ( fullWidth > firstSegmentWidth ) ? ( fullWidth - firstSegmentWidth ) / 2.0 : 0.0;
435 }
436 else
437 {
438 firstLabelWidth = fullWidth / 2.0;
439 }
440 }
441
442 const double largestLabelNumber = settings.numberOfSegments() * settings.unitsPerSegment() / settings.mapUnitsPerScaleBarUnit();
443 const QString largestNumberLabel = std::isnan( largestLabelNumber ) ? QString() : settings.numericFormat()->formatDouble( largestLabelNumber, QgsNumericFormatContext() );
444
445 QString largestLabel = largestNumberLabel;
446 if ( hasUnitLabel )
447 {
448 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeEveryDistanceLabel ) )
449 {
450 largestLabel = unitLabel + largestLabel;
451 }
453 {
454 largestLabel = largestLabel + unitLabel;
455 }
456 }
457
458 double largestLabelWidth;
460 {
461 largestLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm;
462
463 if ( largestLabelWidth > scaleContext.segmentWidth )
464 {
465 largestLabelWidth = ( largestLabelWidth - scaleContext.segmentWidth ) / 2.0;
466 }
467 else
468 {
469 largestLabelWidth = 0.0;
470 }
471 }
472 else
473 {
474 if ( hasUnitLabel
478 {
479 largestLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm
480 - QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestNumberLabel ) * painterToMm / 2.0;
481 }
482 else
483 {
484 largestLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm / 2.0;
485 }
486 }
487
488 // segmentWidth can be NaN in extreme cases, eg trying to make a scalebar for a global map with a very small segment size (eg meters)
489 const double totalBarLength = std::isnan( scaleContext.segmentWidth ) ? 0 : scaleContext.segmentWidth * ( settings.numberOfSegments() + ( settings.numberOfSegmentsLeft() > 0 ? 1 : 0 ) );
490
491 double lineWidth = QgsSymbolLayerUtils::estimateMaxSymbolBleed( settings.lineSymbol(), context ) * 2.0;
492 // need to convert to mm
493 lineWidth /= context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
494
495 double leftExtension = firstLabelWidth;
496 double rightExtension = largestLabelWidth;
497
498 if ( hasUnitLabel )
499 {
500 const double unitLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << unitLabel ) * painterToMm;
501 const double labelBarSpaceMM = settings.labelBarSpace();
502
503 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeBar ) )
504 {
505 leftExtension = std::max( leftExtension, unitLabelWidth + labelBarSpaceMM );
506 }
507 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::AfterBar ) )
508 {
509 rightExtension = std::max( rightExtension, unitLabelWidth + labelBarSpaceMM );
510 }
511 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftBelow ) )
512 {
513 leftExtension = std::max( leftExtension, unitLabelWidth / 2.0 );
514 }
515 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightBelow ) )
516 {
517 rightExtension = std::max( rightExtension, unitLabelWidth / 2.0 );
518 }
519 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredBelow ) )
520 {
521 const double centerOverhang = ( unitLabelWidth - totalBarLength ) / 2.0;
522 if ( centerOverhang > 0 )
523 {
524 leftExtension = std::max( leftExtension, centerOverhang );
525 rightExtension = std::max( rightExtension, centerOverhang );
526 }
527 }
528 }
529
530 const double width = leftExtension + totalBarLength + 2.0 * lineWidth + rightExtension + 2.0 * settings.boxContentSpace();
531 double barHeight;
532 if ( ( scaleContext.flags & Flag::FlagUsesSubdivisionsHeight ) && ( settings.numberOfSubdivisions() > 1 ) && ( settings.subdivisionsHeight() > settings.height() ) )
533 {
534 barHeight = settings.subdivisionsHeight();
535 }
536 else
537 {
538 barHeight = settings.height();
539 }
540
541 double height = barHeight + 2 * settings.boxContentSpace();
542
543 // TODO -- we technically should check the height of ALL labels here and take the maximum
544 const double textHeight = QgsTextRenderer::textHeight( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm;
545
546 const bool hasDistanceLabelsAbove = ( settings.labelVerticalPlacement() == Qgis::ScaleBarDistanceLabelVerticalPlacement::AboveSegment );
547 const bool hasDistanceLabelsBelow = ( settings.labelVerticalPlacement() == Qgis::ScaleBarDistanceLabelVerticalPlacement::BelowSegment );
548
549 bool hasUnitLabelAbove = false;
550 bool hasUnitLabelBelow = false;
551 if ( hasUnitLabel )
552 {
553 hasUnitLabelAbove = unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftAbove )
554 || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredAbove )
555 || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightAbove );
556 hasUnitLabelBelow = unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftBelow )
557 || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredBelow )
558 || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightBelow );
559 }
560
561 double topSpace = 0.0;
562 if ( hasDistanceLabelsAbove )
563 {
564 topSpace += settings.labelBarSpace() + textHeight;
565 }
566 if ( hasUnitLabelAbove )
567 {
568 const double unitTextHeight = QgsTextRenderer::textHeight( context, settings.textFormat(), QStringList() << unitLabel ) * painterToMm;
569 topSpace += settings.labelBarSpace() + unitTextHeight;
570 }
571
572 double bottomSpace = 0.0;
573 if ( hasDistanceLabelsBelow )
574 {
575 bottomSpace += settings.labelBarSpace() + textHeight;
576 }
577 if ( hasUnitLabelBelow )
578 {
579 const double unitTextHeight = QgsTextRenderer::textHeight( context, settings.textFormat(), QStringList() << unitLabel ) * painterToMm;
580 bottomSpace += settings.labelBarSpace() + unitTextHeight;
581 }
582
583 height += topSpace + bottomSpace;
584
585 return QSizeF( width, height );
586}
587
589{
590 return false;
591}
592
594{
595 QString label;
596 if ( settings.numberOfSegmentsLeft() > 0 )
597 {
598 label = settings.numericFormat()->formatDouble( settings.unitsPerSegment() / settings.mapUnitsPerScaleBarUnit(), QgsNumericFormatContext() );
599 }
600 else
601 {
602 label = settings.numericFormat()->formatDouble( 0, QgsNumericFormatContext() );
603 }
604
605 if ( !settings.unitLabel().isEmpty() )
606 {
607 const Qgis::ScaleBarUnitLabelPlacements placements = settings.unitLabelPlacements();
609 {
610 label = settings.unitLabel() + label;
611 }
613 {
614 label = label + settings.unitLabel();
615 }
616 }
617
618 return label;
619}
620
622{
623 const QString firstLabel = firstLabelString( settings );
625 return QgsLayoutUtils::textWidthMM( settings.font(), firstLabel ) / 2.0;
627}
628
629double QgsScaleBarRenderer::firstLabelXOffset( const QgsScaleBarSettings &settings, const QgsRenderContext &context, const ScaleBarContext &scaleContext ) const
630{
631 const Qgis::ScaleBarUnitLabelPlacements unitPlacements = settings.unitLabelPlacements();
632 const QString unitLabel = settings.unitLabel();
633 const bool hasUnitLabel = !unitLabel.isEmpty();
634
635 const QString firstNumLabel = ( settings.numberOfSegmentsLeft() > 0 )
638
639 const double firstSegmentWidth = ( settings.numberOfSegmentsLeft() > 0 ) ? ( scaleContext.segmentWidth / settings.numberOfSegmentsLeft() ) : scaleContext.segmentWidth;
640 const double firstSegmentWidthPx = context.convertToPainterUnits( firstSegmentWidth, Qgis::RenderUnit::Millimeters );
641
642 double firstLabelWidth = 0.0;
643
644 if ( hasUnitLabel && unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeFirstDistanceLabel ) && !unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeEveryDistanceLabel ) )
645 {
646 const QString fullFirstLabel = unitLabel + firstNumLabel;
647 const double fullWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << fullFirstLabel );
648 const double numWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << firstNumLabel );
649
651 {
652 if ( fullWidth > firstSegmentWidthPx )
653 {
654 firstLabelWidth = ( fullWidth - firstSegmentWidthPx ) / 2.0;
655 }
656 else
657 {
658 firstLabelWidth = 0.0;
659 }
660 }
661 else
662 {
663 firstLabelWidth = fullWidth - numWidth / 2.0;
664 }
665 }
666 else
667 {
668 const QString fullFirstLabel = firstLabelString( settings );
669 const double fullWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << fullFirstLabel );
670
672 {
673 if ( fullWidth > firstSegmentWidthPx )
674 {
675 firstLabelWidth = ( fullWidth - firstSegmentWidthPx ) / 2.0;
676 }
677 else
678 {
679 firstLabelWidth = 0.0;
680 }
681 }
682 else
683 {
684 firstLabelWidth = fullWidth / 2.0;
685 }
686 }
687
688 double xOffset = firstLabelWidth;
689
690 if ( hasUnitLabel )
691 {
692 const double unitLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << unitLabel );
693 const double scaledLabelBarSpace = context.convertToPainterUnits( settings.labelBarSpace(), Qgis::RenderUnit::Millimeters );
694
695 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeBar ) )
696 {
697 xOffset = std::max( xOffset, unitLabelWidth + scaledLabelBarSpace );
698 }
699 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftBelow ) )
700 {
701 xOffset = std::max( xOffset, unitLabelWidth / 2.0 );
702 }
703 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredBelow ) )
704 {
705 const double totalBarLengthMM = scaleContext.segmentWidth * ( settings.numberOfSegments() + ( settings.numberOfSegmentsLeft() > 0 ? 1 : 0 ) );
706 const double totalBarLengthPx = context.convertToPainterUnits( totalBarLengthMM, Qgis::RenderUnit::Millimeters );
707 const double centerOverhang = ( unitLabelWidth - totalBarLengthPx ) / 2.0;
708 if ( centerOverhang > 0 )
709 {
710 xOffset = std::max( xOffset, centerOverhang );
711 }
712 }
713 }
714
715 return xOffset;
716}
717
718QList<double> QgsScaleBarRenderer::segmentPositions( const ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const
719{
720 QList<double> positions;
721
722 // this whole method is deprecated, so calling a deprecated function is fine
724 double currentXCoord = settings.pen().widthF() + settings.boxContentSpace();
726
727 // left segments
728 const double leftSegmentSize = scaleContext.segmentWidth / settings.numberOfSegmentsLeft();
729 positions.reserve( settings.numberOfSegmentsLeft() + settings.numberOfSegments() );
730 for ( int i = 0; i < settings.numberOfSegmentsLeft(); ++i )
731 {
732 positions << currentXCoord;
733 currentXCoord += leftSegmentSize;
734 }
735
736 // right segments
737 for ( int i = 0; i < settings.numberOfSegments(); ++i )
738 {
739 positions << currentXCoord;
740 currentXCoord += scaleContext.segmentWidth;
741 }
742 return positions;
743}
744
746{
747 QList<double> positions;
748
749 double lineWidth = QgsSymbolLayerUtils::estimateMaxSymbolBleed( settings.lineSymbol(), context ) * 2.0;
750 // need to convert to mm
751 lineWidth /= context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
752
753 double currentXCoord = lineWidth + settings.boxContentSpace();
754
755 // left segments
756 const double leftSegmentSize = scaleContext.segmentWidth / settings.numberOfSegmentsLeft();
757 positions.reserve( settings.numberOfSegmentsLeft() + settings.numberOfSegments() );
758 for ( int i = 0; i < settings.numberOfSegmentsLeft(); ++i )
759 {
760 positions << currentXCoord;
761 currentXCoord += leftSegmentSize;
762 }
763
764 // right segments
765 for ( int i = 0; i < settings.numberOfSegments(); ++i )
766 {
767 positions << currentXCoord;
768 currentXCoord += scaleContext.segmentWidth;
769 }
770 return positions;
771}
772
773QList<double> QgsScaleBarRenderer::segmentWidths( const ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const
774{
775 QList<double> widths;
776 widths.reserve( settings.numberOfSegmentsLeft() + settings.numberOfSegments() );
777
778 // left segments
779 if ( settings.numberOfSegmentsLeft() > 0 )
780 {
781 const double leftSegmentSize = scaleContext.segmentWidth / settings.numberOfSegmentsLeft();
782 for ( int i = 0; i < settings.numberOfSegmentsLeft(); ++i )
783 {
784 widths << leftSegmentSize;
785 }
786 }
787
788 // right segments
789 for ( int i = 0; i < settings.numberOfSegments(); ++i )
790 {
791 widths << scaleContext.segmentWidth;
792 }
793
794 return widths;
795}
796
798{
799 const double scaledLabelBarSpace = context.convertToPainterUnits( settings.labelBarSpace(), Qgis::RenderUnit::Millimeters );
800 const double scaledBoxContentSpace = context.convertToPainterUnits( settings.boxContentSpace(), Qgis::RenderUnit::Millimeters );
801 const QFontMetricsF fontMetrics = QgsTextRenderer::fontMetrics( context, settings.textFormat() );
802
803 const Qgis::ScaleBarUnitLabelPlacements unitPlacements = settings.unitLabelPlacements();
804 const bool hasUnitLabelAbove
805 = !settings.unitLabel().isEmpty()
806 && ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightAbove ) );
807
808 double barTopPosition = scaledBoxContentSpace;
809 if ( hasUnitLabelAbove )
810 {
811 barTopPosition += fontMetrics.ascent() + scaledLabelBarSpace;
812 }
814 {
815 barTopPosition += fontMetrics.ascent() + scaledLabelBarSpace;
816 }
817
818 return barTopPosition;
819}
820
822{
823 return !std::isnan( segmentWidth );
824}
@ CenteredSegment
Labels are drawn centered relative to segment.
Definition qgis.h:5927
@ CenteredEdge
Labels are drawn centered relative to segment's edge.
Definition qgis.h:5926
@ AboveSegment
Labels are drawn above the scalebar.
Definition qgis.h:5912
@ BelowSegment
Labels are drawn below the scalebar.
Definition qgis.h:5913
@ AfterLastDistanceLabel
Placed after the last distance label.
Definition qgis.h:5949
@ CenteredBelow
Horizontally centered on the bar, below the bar.
Definition qgis.h:5946
@ LeftAbove
Text centered over the left edge of the bar, above the bar.
Definition qgis.h:5942
@ AfterBar
Vertically centered on the bar, after the bar ends.
Definition qgis.h:5941
@ BeforeBar
Vertically centered on the bar, before the bar begins.
Definition qgis.h:5940
@ BeforeEveryDistanceLabel
Placed before every distance label (as part of the distance label text).
Definition qgis.h:5951
@ CenteredAbove
Horizontally centered on the bar, above the bar.
Definition qgis.h:5943
@ RightBelow
Text centered over the right edge of the bar, below the bar.
Definition qgis.h:5947
@ OnBarAfterFirstDivision
Placed on top of the bar, after the first bar division.
Definition qgis.h:5950
@ BeforeFirstDistanceLabel
Placed before the first distance label.
Definition qgis.h:5948
@ RightAbove
Text centered over the right edge of the bar, above the bar.
Definition qgis.h:5944
@ AfterEveryDistanceLabel
Placed after every distance label (as part of the distance label text).
Definition qgis.h:5952
@ LeftBelow
Text centered over the left edge of the bar, below the bar.
Definition qgis.h:5945
QFlags< ScaleBarUnitLabelPlacement > ScaleBarUnitLabelPlacements
Available placement options for a scale bar's unit label.
Definition qgis.h:5963
@ Millimeters
Millimeters.
Definition qgis.h:5759
@ Center
Center align.
Definition qgis.h:3169
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.
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).
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
double verticalOffset(QgsRenderContext &context, const QgsScaleBarSettings &settings) const
Calculates the vertical offset to the top of the bar, accounting for text placed above the bar.
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.
@ FlagUsesSubdivisionsHeight
Renderer uses the scalebar subdivisions height (see QgsScaleBarSettings::subdivisionsHeight() ).
Stores the appearance and layout settings for scalebar drawing with QgsScaleBarRenderer.
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.
Qgis::ScaleBarUnitLabelPlacements unitLabelPlacements() const
Returns the placements for unit labels.
Qgis::ScaleBarDistanceLabelVerticalPlacement labelVerticalPlacement() const
Returns the vertical placement of text labels.
double unitsPerSegment() const
Returns the number of scalebar units per segment.
QgsTextFormat & textFormat()
Returns the text format used for drawing text in the scalebar.
Q_DECL_DEPRECATED QPen pen() const
Returns the pen used for drawing outlines in the scalebar.
double boxContentSpace() const
Returns the spacing (margin) between the scalebar box and content in millimeters.
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...
Qgis::ScaleBarDistanceLabelHorizontalPlacement labelHorizontalPlacement() const
Returns the horizontal placement of text labels.
Q_DECL_DEPRECATED QFont font() const
Returns the font used for drawing text in the scalebar.
double labelBarSpace() const
Returns the spacing (in millimeters) between labels and 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.
Scoped object for saving and restoring a QPainter object's state.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
Container for all settings relating to text rendering.
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.
static void drawText(const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, Qgis::TextVerticalAlignment vAlignment=Qgis::TextVerticalAlignment::Top, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle)
Draws text within a rectangle using the specified settings.
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 double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Point, QFontMetricsF *fontMetrics=nullptr, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), double maxLineWidth=0)
Returns the height of a text based on a given format.
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:8379
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:8378
Single variable definition for use within a QgsExpressionContextScope.
Contains parameters regarding scalebar calculations.
bool isValid() const
Returns true if the context has valid settings.
QgsScaleBarRenderer::Flags flags
Scalebar renderer flags.
double segmentWidth
The width, in millimeters, of each individual segment drawn.