QGIS API Documentation 4.3.0-Master (0cfde48c85b)
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 "qgsscalebarsettings.h"
24#include "qgssymbol.h"
25#include "qgssymbollayerutils.h"
26#include "qgstextrenderer.h"
27
28#include <QFontMetricsF>
29#include <QPainter>
30#include <QString>
31
32using namespace Qt::StringLiterals;
33
34void QgsScaleBarRenderer::drawDefaultLabels( QgsRenderContext &context, const QgsScaleBarSettings &settings, const ScaleBarContext &scaleContext ) const
35{
36 if ( !context.painter() )
37 {
38 return;
39 }
40
41 QPainter *painter = context.painter();
42
43 painter->save();
44
45 const QgsTextFormat format = settings.textFormat();
46
47 QgsExpressionContextScope *scaleScope = new QgsExpressionContextScope( u"scalebar_text"_s );
48 const QgsExpressionContextScopePopper scopePopper( context.expressionContext(), scaleScope );
49
50 const QString firstLabel = firstLabelString( settings );
51 const QFontMetricsF fontMetrics = QgsTextRenderer::fontMetrics( context, format );
52 const double xOffset = firstLabelXOffset( settings, context, scaleContext );
53
54 const double scaledBoxContentSpace = context.convertToPainterUnits( settings.boxContentSpace(), Qgis::RenderUnit::Millimeters );
55 const double scaledLabelBarSpace = context.convertToPainterUnits( settings.labelBarSpace(), Qgis::RenderUnit::Millimeters );
56 double scaledHeight;
57 if ( ( scaleContext.flags & Flag::FlagUsesSubdivisionsHeight ) && ( settings.numberOfSubdivisions() > 1 ) && ( settings.subdivisionsHeight() > settings.height() ) )
58 {
60 }
61 else
62 {
63 scaledHeight = context.convertToPainterUnits( settings.height(), Qgis::RenderUnit::Millimeters );
64 }
65
66 const Qgis::ScaleBarUnitLabelPlacements unitPlacements = settings.unitLabelPlacements();
67 const QString unitLabel = settings.unitLabel();
68 const bool hasUnitLabel = !unitLabel.isEmpty();
69
70 const bool hasUnitLabelAbove
71 = hasUnitLabel
72 && ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightAbove ) );
73
74 const bool hasDistanceLabelsAbove = ( settings.labelVerticalPlacement() == Qgis::ScaleBarDistanceLabelVerticalPlacement::AboveSegment );
75 const bool hasDistanceLabelsBelow = ( settings.labelVerticalPlacement() == Qgis::ScaleBarDistanceLabelVerticalPlacement::BelowSegment );
76
77 double barTopPosition = scaledBoxContentSpace;
78 if ( hasUnitLabelAbove )
79 {
80 barTopPosition += fontMetrics.ascent() + scaledLabelBarSpace;
81 }
82 if ( hasDistanceLabelsAbove )
83 {
84 barTopPosition += fontMetrics.ascent() + scaledLabelBarSpace;
85 }
86
87 const double barBottomPosition = barTopPosition + scaledHeight;
88 const double barMiddleY = barTopPosition + scaledHeight / 2.0;
89
90 double distanceLabelY = 0.0;
91 if ( hasDistanceLabelsAbove )
92 {
93 distanceLabelY = barTopPosition - scaledLabelBarSpace;
94 }
95 else if ( hasDistanceLabelsBelow )
96 {
97 distanceLabelY = barBottomPosition + scaledLabelBarSpace + fontMetrics.ascent();
98 }
99
100 const QList<double> positions = segmentPositions( context, scaleContext, settings );
101 const int nSegmentsLeft = settings.numberOfSegmentsLeft();
102 const QgsNumericFormatContext numericContext;
103
104 switch ( settings.labelHorizontalPlacement() )
105 {
107 {
108 for ( int i = 0; i < positions.size(); ++i )
109 {
110 const bool isFirstLabel = ( i == 0 );
111 const bool isLastLabel = ( i == positions.size() - 1 );
112
113 QString currentNumericLabel;
114 if ( i < nSegmentsLeft )
115 {
116 if ( i == 0 )
117 {
118 currentNumericLabel = firstLabel;
119 }
120 else
121 {
122 // skip intermediate left segments
123 continue;
124 }
125 }
126 else
127 {
128 const double val = ( i - nSegmentsLeft + 1 ) * settings.unitsPerSegment();
129 currentNumericLabel = settings.numericFormat()->formatDouble( val / settings.mapUnitsPerScaleBarUnit(), numericContext );
130 }
131
132 const double segmentWidthMM = ( i < nSegmentsLeft && nSegmentsLeft > 0 ) ? ( scaleContext.segmentWidth / nSegmentsLeft ) : scaleContext.segmentWidth;
133
134 const QPointF pos( context.convertToPainterUnits( positions.at( i ) + ( segmentWidthMM / 2.0 ), Qgis::RenderUnit::Millimeters ) + xOffset, distanceLabelY );
135
136 QString labelText = currentNumericLabel;
137 if ( hasUnitLabel )
138 {
140 || ( isFirstLabel && unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeFirstDistanceLabel ) ) )
141 {
142 labelText = unitLabel + labelText;
143 }
145 || ( isLastLabel && unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::AfterLastDistanceLabel ) ) )
146 {
147 labelText = labelText + unitLabel;
148 }
149 }
150
151 scaleScope->addVariable( QgsExpressionContextScope::StaticVariable( u"scale_value"_s, currentNumericLabel, true, false ) );
152 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << labelText, context, format );
153 }
154 break;
155 }
156
158 {
159 double currentLabelNumber = 0.0;
160 int segmentCounter = 0;
161 QString currentNumericLabel;
162
163 for ( int i = 0; i < positions.size(); ++i )
164 {
165 if ( segmentCounter == 0 && nSegmentsLeft > 0 )
166 {
167 // label first left segment
168 currentNumericLabel = firstLabel;
169 }
170 else if ( segmentCounter != 0 && segmentCounter == nSegmentsLeft )
171 {
172 // reset label number to 0 if there are left segments
173 currentLabelNumber = 0.0;
174 }
175
176 if ( segmentCounter >= nSegmentsLeft )
177 {
178 currentNumericLabel = settings.numericFormat()->formatDouble( currentLabelNumber / settings.mapUnitsPerScaleBarUnit(), numericContext );
179 }
180
181 // don't draw label for intermediate left segments
182 if ( segmentCounter == 0 || segmentCounter >= nSegmentsLeft )
183 {
184 const bool isFirstLabel = ( segmentCounter == 0 );
185
186 scaleScope->addVariable( QgsExpressionContextScope::StaticVariable( u"scale_value"_s, currentNumericLabel, true, false ) );
187
188 QPointF pos( context.convertToPainterUnits( positions.at( i ), Qgis::RenderUnit::Millimeters ) + xOffset, distanceLabelY );
189
190 if ( isFirstLabel
191 && hasUnitLabel
194 {
195 pos.setX( pos.x() + fontMetrics.horizontalAdvance( currentNumericLabel ) / 2.0 );
196 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Right, QStringList() << ( unitLabel + currentNumericLabel ), context, format );
197 }
198 else
199 {
200 QString labelText = currentNumericLabel;
201 if ( hasUnitLabel )
202 {
203 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeEveryDistanceLabel ) )
204 {
205 labelText = unitLabel + labelText;
206 }
207 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::AfterEveryDistanceLabel ) )
208 {
209 labelText = labelText + unitLabel;
210 }
211 }
212 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << labelText, context, format );
213 }
214 }
215
216 if ( segmentCounter >= nSegmentsLeft )
217 {
218 currentLabelNumber += settings.unitsPerSegment();
219 }
220 ++segmentCounter;
221 }
222
223 // draw last label
224 if ( !positions.isEmpty() )
225 {
226 currentNumericLabel = settings.numericFormat()->formatDouble( currentLabelNumber / settings.mapUnitsPerScaleBarUnit(), numericContext );
227 QString lastLabelText = currentNumericLabel;
228
229 const bool hasSuffix
230 = hasUnitLabel
232 const bool hasPrefix = hasUnitLabel && unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeEveryDistanceLabel );
233 if ( hasPrefix )
234 {
235 lastLabelText = unitLabel + lastLabelText;
236 }
237 if ( hasSuffix )
238 {
239 lastLabelText = lastLabelText + unitLabel;
240 }
241
242 scaleScope->addVariable( QgsExpressionContextScope::StaticVariable( u"scale_value"_s, currentNumericLabel, true, false ) );
243 QPointF pos;
244 pos.setY( distanceLabelY );
245
246 if ( hasSuffix
247 && !hasPrefix
249 && !unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::AfterEveryDistanceLabel ) )
250 {
251 pos.setX(
252 context.convertToPainterUnits( positions.at( positions.size() - 1 ) + scaleContext.segmentWidth, Qgis::RenderUnit::Millimeters )
253 + xOffset
254 - fontMetrics.horizontalAdvance( currentNumericLabel ) / 2.0
255 );
256 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Left, QStringList() << ( currentNumericLabel + unitLabel ), context, format );
257 }
258 else
259 {
260 pos.setX( context.convertToPainterUnits( positions.at( positions.size() - 1 ) + scaleContext.segmentWidth, Qgis::RenderUnit::Millimeters ) + xOffset );
261 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << lastLabelText, context, format );
262 }
263 }
264 break;
265 }
266 }
267
268 // draw standalone unit labels
269 if ( hasUnitLabel && !positions.isEmpty() )
270 {
271 const double barLeftX = context.convertToPainterUnits( positions.first(), Qgis::RenderUnit::Millimeters ) + xOffset;
272 const double barRightX = context.convertToPainterUnits( positions.last() + scaleContext.segmentWidth, Qgis::RenderUnit::Millimeters ) + xOffset;
273
274 const double unitLabelYAbove = scaledBoxContentSpace + fontMetrics.ascent();
275
276 double unitLabelYBelow = barBottomPosition + scaledLabelBarSpace + fontMetrics.ascent();
277 if ( hasDistanceLabelsBelow )
278 {
279 unitLabelYBelow += fontMetrics.ascent() + scaledLabelBarSpace;
280 }
281
282 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeBar ) )
283 {
284 const QPointF pos( barLeftX - scaledLabelBarSpace, barMiddleY + ( fontMetrics.ascent() - fontMetrics.descent() ) / 2.0 );
285 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Right, QStringList() << unitLabel, context, format );
286 }
287 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::AfterBar ) )
288 {
289 const QPointF pos( barRightX + scaledLabelBarSpace, barMiddleY + ( fontMetrics.ascent() - fontMetrics.descent() ) / 2.0 );
290 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Left, QStringList() << unitLabel, context, format );
291 }
292 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftAbove ) )
293 {
294 const QPointF pos( barLeftX, unitLabelYAbove );
295 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
296 }
297 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredAbove ) )
298 {
299 const QPointF pos( ( barLeftX + barRightX ) / 2.0, unitLabelYAbove );
300 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
301 }
302 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightAbove ) )
303 {
304 const QPointF pos( barRightX, unitLabelYAbove );
305 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
306 }
307 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftBelow ) )
308 {
309 const QPointF pos( barLeftX, unitLabelYBelow );
310 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
311 }
312 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredBelow ) )
313 {
314 const QPointF pos( ( barLeftX + barRightX ) / 2.0, unitLabelYBelow );
315 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
316 }
317 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightBelow ) )
318 {
319 const QPointF pos( barRightX, unitLabelYBelow );
320 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
321 }
322 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::OnBarAfterFirstDivision ) )
323 {
324 const double firstDivCenterX = context.convertToPainterUnits( positions.value( nSegmentsLeft, positions.first() ) + scaleContext.segmentWidth / 2.0, Qgis::RenderUnit::Millimeters ) + xOffset;
325 const QPointF pos( firstDivCenterX, barMiddleY + ( fontMetrics.ascent() - fontMetrics.descent() ) / 2.0 );
326 QgsTextRenderer::drawText( pos, 0, Qgis::TextHorizontalAlignment::Center, QStringList() << unitLabel, context, format );
327 }
328 }
329
330 painter->restore();
331}
332
337
339{
340 return 100;
341}
342
344{
345 const QFont font = settings.textFormat().toQFont();
346
347 // consider centered first label
348 double firstLabelWidth = QgsLayoutUtils::textWidthMM( font, firstLabelString( settings ) );
350 {
351 if ( firstLabelWidth > scaleContext.segmentWidth )
352 {
353 firstLabelWidth = ( firstLabelWidth - scaleContext.segmentWidth ) / 2.0;
354 }
355 else
356 {
357 firstLabelWidth = 0.0;
358 }
359 }
360 else
361 {
362 firstLabelWidth /= 2.0;
363 }
364
365 // consider last number and label
366 const double largestLabelNumber = settings.numberOfSegments() * settings.unitsPerSegment() / settings.mapUnitsPerScaleBarUnit();
367 const QString largestNumberLabel = settings.numericFormat()->formatDouble( largestLabelNumber, QgsNumericFormatContext() );
368 const QString largestLabel = largestNumberLabel + settings.unitLabel();
369 double largestLabelWidth;
371 {
372 largestLabelWidth = QgsLayoutUtils::textWidthMM( font, largestLabel );
373 if ( largestLabelWidth > scaleContext.segmentWidth )
374 {
375 largestLabelWidth = ( largestLabelWidth - scaleContext.segmentWidth ) / 2.0;
376 }
377 else
378 {
379 largestLabelWidth = 0.0;
380 }
381 }
382 else
383 {
384 largestLabelWidth = QgsLayoutUtils::textWidthMM( font, largestLabel ) - QgsLayoutUtils::textWidthMM( font, largestNumberLabel ) / 2.0;
385 }
386
387 const double totalBarLength = scaleContext.segmentWidth * ( settings.numberOfSegments() + ( settings.numberOfSegmentsLeft() > 0 ? 1 : 0 ) );
388
389 // this whole method is deprecated, so we can still call the deprecated settings.pen() getter
391 const double width = firstLabelWidth + totalBarLength + 2.0 * settings.pen().widthF() + largestLabelWidth + 2.0 * settings.boxContentSpace();
393
394 const double height = settings.height() + settings.labelBarSpace() + 2.0 * settings.boxContentSpace() + QgsLayoutUtils::fontAscentMM( font );
395
396 return QSizeF( width, height );
397}
398
400{
401 const double painterToMm = 1.0 / context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
402 const Qgis::ScaleBarUnitLabelPlacements unitPlacements = settings.unitLabelPlacements();
403 const QString unitLabel = settings.unitLabel();
404 const bool hasUnitLabel = !unitLabel.isEmpty();
405
406 const QString firstNumLabel = ( settings.numberOfSegmentsLeft() > 0 )
409
410 const double firstSegmentWidth = ( settings.numberOfSegmentsLeft() > 0 ) ? ( scaleContext.segmentWidth / settings.numberOfSegmentsLeft() ) : scaleContext.segmentWidth;
411
412 double firstLabelWidth = 0.0;
413 if ( hasUnitLabel && unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeFirstDistanceLabel ) && !unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeEveryDistanceLabel ) )
414 {
415 const QString fullFirstLabel = unitLabel + firstNumLabel;
416 const double fullWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << fullFirstLabel ) * painterToMm;
417 const double numWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << firstNumLabel ) * painterToMm;
418
420 {
421 firstLabelWidth = ( fullWidth > firstSegmentWidth ) ? ( fullWidth - firstSegmentWidth ) / 2.0 : 0.0;
422 }
423 else
424 {
425 firstLabelWidth = fullWidth - numWidth / 2.0;
426 }
427 }
428 else
429 {
430 const QString fullFirstLabel = firstLabelString( settings );
431 const double fullWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << fullFirstLabel ) * painterToMm;
432
434 {
435 firstLabelWidth = ( fullWidth > firstSegmentWidth ) ? ( fullWidth - firstSegmentWidth ) / 2.0 : 0.0;
436 }
437 else
438 {
439 firstLabelWidth = fullWidth / 2.0;
440 }
441 }
442
443 const double largestLabelNumber = settings.numberOfSegments() * settings.unitsPerSegment() / settings.mapUnitsPerScaleBarUnit();
444 const QString largestNumberLabel = std::isnan( largestLabelNumber ) ? QString() : settings.numericFormat()->formatDouble( largestLabelNumber, QgsNumericFormatContext() );
445
446 QString largestLabel = largestNumberLabel;
447 if ( hasUnitLabel )
448 {
449 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeEveryDistanceLabel ) )
450 {
451 largestLabel = unitLabel + largestLabel;
452 }
454 {
455 largestLabel = largestLabel + unitLabel;
456 }
457 }
458
459 double largestLabelWidth;
461 {
462 largestLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm;
463
464 if ( largestLabelWidth > scaleContext.segmentWidth )
465 {
466 largestLabelWidth = ( largestLabelWidth - scaleContext.segmentWidth ) / 2.0;
467 }
468 else
469 {
470 largestLabelWidth = 0.0;
471 }
472 }
473 else
474 {
475 if ( hasUnitLabel
479 {
480 largestLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm
481 - QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestNumberLabel ) * painterToMm / 2.0;
482 }
483 else
484 {
485 largestLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm / 2.0;
486 }
487 }
488
489 // 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)
490 const double totalBarLength = std::isnan( scaleContext.segmentWidth ) ? 0 : scaleContext.segmentWidth * ( settings.numberOfSegments() + ( settings.numberOfSegmentsLeft() > 0 ? 1 : 0 ) );
491
492 double lineWidth = QgsSymbolLayerUtils::estimateMaxSymbolBleed( settings.lineSymbol(), context ) * 2.0;
493 // need to convert to mm
494 lineWidth /= context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
495
496 double leftExtension = firstLabelWidth;
497 double rightExtension = largestLabelWidth;
498
499 if ( hasUnitLabel )
500 {
501 const double unitLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << unitLabel ) * painterToMm;
502 const double labelBarSpaceMM = settings.labelBarSpace();
503
504 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeBar ) )
505 {
506 leftExtension = std::max( leftExtension, unitLabelWidth + labelBarSpaceMM );
507 }
508 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::AfterBar ) )
509 {
510 rightExtension = std::max( rightExtension, unitLabelWidth + labelBarSpaceMM );
511 }
512 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftBelow ) )
513 {
514 leftExtension = std::max( leftExtension, unitLabelWidth / 2.0 );
515 }
516 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightBelow ) )
517 {
518 rightExtension = std::max( rightExtension, unitLabelWidth / 2.0 );
519 }
520 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredBelow ) )
521 {
522 const double centerOverhang = ( unitLabelWidth - totalBarLength ) / 2.0;
523 if ( centerOverhang > 0 )
524 {
525 leftExtension = std::max( leftExtension, centerOverhang );
526 rightExtension = std::max( rightExtension, centerOverhang );
527 }
528 }
529 }
530
531 const double width = leftExtension + totalBarLength + 2.0 * lineWidth + rightExtension + 2.0 * settings.boxContentSpace();
532 double barHeight;
533 if ( ( scaleContext.flags & Flag::FlagUsesSubdivisionsHeight ) && ( settings.numberOfSubdivisions() > 1 ) && ( settings.subdivisionsHeight() > settings.height() ) )
534 {
535 barHeight = settings.subdivisionsHeight();
536 }
537 else
538 {
539 barHeight = settings.height();
540 }
541
542 double height = barHeight + 2 * settings.boxContentSpace();
543
544 // TODO -- we technically should check the height of ALL labels here and take the maximum
545 const double textHeight = QgsTextRenderer::textHeight( context, settings.textFormat(), QStringList() << largestLabel ) * painterToMm;
546
547 const bool hasDistanceLabelsAbove = ( settings.labelVerticalPlacement() == Qgis::ScaleBarDistanceLabelVerticalPlacement::AboveSegment );
548 const bool hasDistanceLabelsBelow = ( settings.labelVerticalPlacement() == Qgis::ScaleBarDistanceLabelVerticalPlacement::BelowSegment );
549
550 bool hasUnitLabelAbove = false;
551 bool hasUnitLabelBelow = false;
552 if ( hasUnitLabel )
553 {
554 hasUnitLabelAbove = unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftAbove )
555 || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredAbove )
556 || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightAbove );
557 hasUnitLabelBelow = unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftBelow )
558 || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredBelow )
559 || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightBelow );
560 }
561
562 double topSpace = 0.0;
563 if ( hasDistanceLabelsAbove )
564 {
565 topSpace += settings.labelBarSpace() + textHeight;
566 }
567 if ( hasUnitLabelAbove )
568 {
569 const double unitTextHeight = QgsTextRenderer::textHeight( context, settings.textFormat(), QStringList() << unitLabel ) * painterToMm;
570 topSpace += settings.labelBarSpace() + unitTextHeight;
571 }
572
573 double bottomSpace = 0.0;
574 if ( hasDistanceLabelsBelow )
575 {
576 bottomSpace += settings.labelBarSpace() + textHeight;
577 }
578 if ( hasUnitLabelBelow )
579 {
580 const double unitTextHeight = QgsTextRenderer::textHeight( context, settings.textFormat(), QStringList() << unitLabel ) * painterToMm;
581 bottomSpace += settings.labelBarSpace() + unitTextHeight;
582 }
583
584 height += topSpace + bottomSpace;
585
586 return QSizeF( width, height );
587}
588
590{
591 return false;
592}
593
595{
596 QString label;
597 if ( settings.numberOfSegmentsLeft() > 0 )
598 {
599 label = settings.numericFormat()->formatDouble( settings.unitsPerSegment() / settings.mapUnitsPerScaleBarUnit(), QgsNumericFormatContext() );
600 }
601 else
602 {
603 label = settings.numericFormat()->formatDouble( 0, QgsNumericFormatContext() );
604 }
605
606 if ( !settings.unitLabel().isEmpty() )
607 {
608 const Qgis::ScaleBarUnitLabelPlacements placements = settings.unitLabelPlacements();
610 {
611 label = settings.unitLabel() + label;
612 }
614 {
615 label = label + settings.unitLabel();
616 }
617 }
618
619 return label;
620}
621
623{
624 const QString firstLabel = firstLabelString( settings );
626 return QgsLayoutUtils::textWidthMM( settings.font(), firstLabel ) / 2.0;
628}
629
630double QgsScaleBarRenderer::firstLabelXOffset( const QgsScaleBarSettings &settings, const QgsRenderContext &context, const ScaleBarContext &scaleContext ) const
631{
632 const Qgis::ScaleBarUnitLabelPlacements unitPlacements = settings.unitLabelPlacements();
633 const QString unitLabel = settings.unitLabel();
634 const bool hasUnitLabel = !unitLabel.isEmpty();
635
636 const QString firstNumLabel = ( settings.numberOfSegmentsLeft() > 0 )
639
640 const double firstSegmentWidth = ( settings.numberOfSegmentsLeft() > 0 ) ? ( scaleContext.segmentWidth / settings.numberOfSegmentsLeft() ) : scaleContext.segmentWidth;
641 const double firstSegmentWidthPx = context.convertToPainterUnits( firstSegmentWidth, Qgis::RenderUnit::Millimeters );
642
643 double firstLabelWidth = 0.0;
644
645 if ( hasUnitLabel && unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeFirstDistanceLabel ) && !unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeEveryDistanceLabel ) )
646 {
647 const QString fullFirstLabel = unitLabel + firstNumLabel;
648 const double fullWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << fullFirstLabel );
649 const double numWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << firstNumLabel );
650
652 {
653 if ( fullWidth > firstSegmentWidthPx )
654 {
655 firstLabelWidth = ( fullWidth - firstSegmentWidthPx ) / 2.0;
656 }
657 else
658 {
659 firstLabelWidth = 0.0;
660 }
661 }
662 else
663 {
664 firstLabelWidth = fullWidth - numWidth / 2.0;
665 }
666 }
667 else
668 {
669 const QString fullFirstLabel = firstLabelString( settings );
670 const double fullWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << fullFirstLabel );
671
673 {
674 if ( fullWidth > firstSegmentWidthPx )
675 {
676 firstLabelWidth = ( fullWidth - firstSegmentWidthPx ) / 2.0;
677 }
678 else
679 {
680 firstLabelWidth = 0.0;
681 }
682 }
683 else
684 {
685 firstLabelWidth = fullWidth / 2.0;
686 }
687 }
688
689 double xOffset = firstLabelWidth;
690
691 if ( hasUnitLabel )
692 {
693 const double unitLabelWidth = QgsTextRenderer::textWidth( context, settings.textFormat(), QStringList() << unitLabel );
694 const double scaledLabelBarSpace = context.convertToPainterUnits( settings.labelBarSpace(), Qgis::RenderUnit::Millimeters );
695
696 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::BeforeBar ) )
697 {
698 xOffset = std::max( xOffset, unitLabelWidth + scaledLabelBarSpace );
699 }
700 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftBelow ) )
701 {
702 xOffset = std::max( xOffset, unitLabelWidth / 2.0 );
703 }
704 if ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredBelow ) )
705 {
706 const double totalBarLengthMM = scaleContext.segmentWidth * ( settings.numberOfSegments() + ( settings.numberOfSegmentsLeft() > 0 ? 1 : 0 ) );
707 const double totalBarLengthPx = context.convertToPainterUnits( totalBarLengthMM, Qgis::RenderUnit::Millimeters );
708 const double centerOverhang = ( unitLabelWidth - totalBarLengthPx ) / 2.0;
709 if ( centerOverhang > 0 )
710 {
711 xOffset = std::max( xOffset, centerOverhang );
712 }
713 }
714 }
715
716 return xOffset;
717}
718
719QList<double> QgsScaleBarRenderer::segmentPositions( const ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const
720{
721 QList<double> positions;
722
723 // this whole method is deprecated, so calling a deprecated function is fine
725 double currentXCoord = settings.pen().widthF() + settings.boxContentSpace();
727
728 // left segments
729 const double leftSegmentSize = scaleContext.segmentWidth / settings.numberOfSegmentsLeft();
730 positions.reserve( settings.numberOfSegmentsLeft() + settings.numberOfSegments() );
731 for ( int i = 0; i < settings.numberOfSegmentsLeft(); ++i )
732 {
733 positions << currentXCoord;
734 currentXCoord += leftSegmentSize;
735 }
736
737 // right segments
738 for ( int i = 0; i < settings.numberOfSegments(); ++i )
739 {
740 positions << currentXCoord;
741 currentXCoord += scaleContext.segmentWidth;
742 }
743 return positions;
744}
745
747{
748 QList<double> positions;
749
750 double lineWidth = QgsSymbolLayerUtils::estimateMaxSymbolBleed( settings.lineSymbol(), context ) * 2.0;
751 // need to convert to mm
752 lineWidth /= context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
753
754 double currentXCoord = lineWidth + settings.boxContentSpace();
755
756 // left segments
757 const double leftSegmentSize = scaleContext.segmentWidth / settings.numberOfSegmentsLeft();
758 positions.reserve( settings.numberOfSegmentsLeft() + settings.numberOfSegments() );
759 for ( int i = 0; i < settings.numberOfSegmentsLeft(); ++i )
760 {
761 positions << currentXCoord;
762 currentXCoord += leftSegmentSize;
763 }
764
765 // right segments
766 for ( int i = 0; i < settings.numberOfSegments(); ++i )
767 {
768 positions << currentXCoord;
769 currentXCoord += scaleContext.segmentWidth;
770 }
771 return positions;
772}
773
774QList<double> QgsScaleBarRenderer::segmentWidths( const ScaleBarContext &scaleContext, const QgsScaleBarSettings &settings ) const
775{
776 QList<double> widths;
777 widths.reserve( settings.numberOfSegmentsLeft() + settings.numberOfSegments() );
778
779 // left segments
780 if ( settings.numberOfSegmentsLeft() > 0 )
781 {
782 const double leftSegmentSize = scaleContext.segmentWidth / settings.numberOfSegmentsLeft();
783 for ( int i = 0; i < settings.numberOfSegmentsLeft(); ++i )
784 {
785 widths << leftSegmentSize;
786 }
787 }
788
789 // right segments
790 for ( int i = 0; i < settings.numberOfSegments(); ++i )
791 {
792 widths << scaleContext.segmentWidth;
793 }
794
795 return widths;
796}
797
799{
800 const double scaledLabelBarSpace = context.convertToPainterUnits( settings.labelBarSpace(), Qgis::RenderUnit::Millimeters );
801 const double scaledBoxContentSpace = context.convertToPainterUnits( settings.boxContentSpace(), Qgis::RenderUnit::Millimeters );
802 const QFontMetricsF fontMetrics = QgsTextRenderer::fontMetrics( context, settings.textFormat() );
803
804 const Qgis::ScaleBarUnitLabelPlacements unitPlacements = settings.unitLabelPlacements();
805 const bool hasUnitLabelAbove
806 = !settings.unitLabel().isEmpty()
807 && ( unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::LeftAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::CenteredAbove ) || unitPlacements.testFlag( Qgis::ScaleBarUnitLabelPlacement::RightAbove ) );
808
809 double barTopPosition = scaledBoxContentSpace;
810 if ( hasUnitLabelAbove )
811 {
812 barTopPosition += fontMetrics.ascent() + scaledLabelBarSpace;
813 }
815 {
816 barTopPosition += fontMetrics.ascent() + scaledLabelBarSpace;
817 }
818
819 return barTopPosition;
820}
821
823{
824 return !std::isnan( segmentWidth );
825}
@ 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.
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:8361
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:8360
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.