1 /***************************************************************************
2                               qgslayoututils.cpp
3                               ------------------
4     begin                : July 2017
5     copyright            : (C) 2017 by Nyall Dawson
6     email                : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgslayoututils.h"
19 #include "qgslayout.h"
20 #include "qgslayoutitemmap.h"
21 #include "qgsprojectviewsettings.h"
22 #include "qgsrendercontext.h"
23 #include "qgssettings.h"
24 
25 #include <QStyleOptionGraphicsItem>
26 #include <QPainter>
27 #include <cmath>
28 
29 #ifndef M_DEG2RAD
30 #define M_DEG2RAD 0.0174532925
31 #endif
32 
rotate(double angle,double & x,double & y)33 void QgsLayoutUtils::rotate( double angle, double &x, double &y )
34 {
35   double rotToRad = angle * M_PI / 180.0;
36   double xRot, yRot;
37   xRot = x * std::cos( rotToRad ) - y * std::sin( rotToRad );
38   yRot = x * std::sin( rotToRad ) + y * std::cos( rotToRad );
39   x = xRot;
40   y = yRot;
41 }
42 
normalizedAngle(const double angle,const bool allowNegative)43 double QgsLayoutUtils::normalizedAngle( const double angle, const bool allowNegative )
44 {
45   double clippedAngle = angle;
46   if ( clippedAngle >= 360.0 || clippedAngle <= -360.0 )
47   {
48     clippedAngle = std::fmod( clippedAngle, 360.0 );
49   }
50   if ( !allowNegative && clippedAngle < 0.0 )
51   {
52     clippedAngle += 360.0;
53   }
54   return clippedAngle;
55 }
56 
snappedAngle(double angle)57 double QgsLayoutUtils::snappedAngle( double angle )
58 {
59   //normalize angle to 0-360 degrees
60   double clippedAngle = normalizedAngle( angle );
61 
62   //snap angle to 45 degree
63   if ( clippedAngle >= 22.5 && clippedAngle < 67.5 )
64   {
65     return 45.0;
66   }
67   else if ( clippedAngle >= 67.5 && clippedAngle < 112.5 )
68   {
69     return 90.0;
70   }
71   else if ( clippedAngle >= 112.5 && clippedAngle < 157.5 )
72   {
73     return 135.0;
74   }
75   else if ( clippedAngle >= 157.5 && clippedAngle < 202.5 )
76   {
77     return 180.0;
78   }
79   else if ( clippedAngle >= 202.5 && clippedAngle < 247.5 )
80   {
81     return 225.0;
82   }
83   else if ( clippedAngle >= 247.5 && clippedAngle < 292.5 )
84   {
85     return 270.0;
86   }
87   else if ( clippedAngle >= 292.5 && clippedAngle < 337.5 )
88   {
89     return 315.0;
90   }
91   else
92   {
93     return 0.0;
94   }
95 }
96 
createRenderContextForMap(QgsLayoutItemMap * map,QPainter * painter,double dpi)97 QgsRenderContext QgsLayoutUtils::createRenderContextForMap( QgsLayoutItemMap *map, QPainter *painter, double dpi )
98 {
99   if ( !map )
100   {
101     QgsRenderContext context;
102     context.setPainter( painter );
103     if ( dpi < 0 && painter && painter->device() )
104     {
105       context.setScaleFactor( painter->device()->logicalDpiX() / 25.4 );
106     }
107     else if ( dpi > 0 )
108     {
109       context.setScaleFactor( dpi / 25.4 );
110     }
111     else
112     {
113       context.setScaleFactor( 3.465 ); //assume 88 dpi as standard value
114     }
115     return context;
116   }
117   else
118   {
119     // default to 88 dpi if no painter specified
120     if ( dpi < 0 )
121     {
122       dpi = ( painter && painter->device() ) ? painter->device()->logicalDpiX() : 88;
123     }
124     double dotsPerMM = dpi / 25.4;
125 
126     // get map settings from reference map
127     QgsRectangle extent = map->extent();
128     QSizeF mapSizeLayoutUnits = map->rect().size();
129     QSizeF mapSizeMM = map->layout()->convertFromLayoutUnits( mapSizeLayoutUnits, QgsUnitTypes::LayoutMillimeters ).toQSizeF();
130     QgsMapSettings ms = map->mapSettings( extent, mapSizeMM * dotsPerMM, dpi, false );
131     QgsRenderContext context = QgsRenderContext::fromMapSettings( ms );
132     if ( painter )
133       context.setPainter( painter );
134 
135     context.setFlags( map->layout()->renderContext().renderContextFlags() );
136     context.setTextRenderFormat( map->layout()->renderContext().textRenderFormat() );
137     return context;
138   }
139 }
140 
createRenderContextForLayout(QgsLayout * layout,QPainter * painter,double dpi)141 QgsRenderContext QgsLayoutUtils::createRenderContextForLayout( QgsLayout *layout, QPainter *painter, double dpi )
142 {
143   QgsLayoutItemMap *referenceMap = layout ? layout->referenceMap() : nullptr;
144   QgsRenderContext context = createRenderContextForMap( referenceMap, painter, dpi );
145   if ( layout )
146   {
147     context.setFlags( layout->renderContext().renderContextFlags() );
148     context.setTextRenderFormat( layout->renderContext().textRenderFormat() );
149   }
150 
151   return context;
152 }
153 
relativeResizeRect(QRectF & rectToResize,const QRectF & boundsBefore,const QRectF & boundsAfter)154 void QgsLayoutUtils::relativeResizeRect( QRectF &rectToResize, const QRectF &boundsBefore, const QRectF &boundsAfter )
155 {
156   //linearly scale rectToResize relative to the scaling from boundsBefore to boundsAfter
157   double left = relativePosition( rectToResize.left(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
158   double right = relativePosition( rectToResize.right(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
159   double top = relativePosition( rectToResize.top(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
160   double bottom = relativePosition( rectToResize.bottom(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
161 
162   rectToResize.setRect( left, top, right - left, bottom - top );
163 }
164 
relativePosition(const double position,const double beforeMin,const double beforeMax,const double afterMin,const double afterMax)165 double QgsLayoutUtils::relativePosition( const double position, const double beforeMin, const double beforeMax, const double afterMin, const double afterMax )
166 {
167   //calculate parameters for linear scale between before and after ranges
168   double m = ( afterMax - afterMin ) / ( beforeMax - beforeMin );
169   double c = afterMin - ( beforeMin * m );
170 
171   //return linearly scaled position
172   return m * position + c;
173 }
scaledFontPixelSize(const QFont & font)174 QFont QgsLayoutUtils::scaledFontPixelSize( const QFont &font )
175 {
176   //upscale using FONT_WORKAROUND_SCALE
177   //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
178   QFont scaledFont = font;
179   double pixelSize = pointsToMM( scaledFont.pointSizeF() ) * FONT_WORKAROUND_SCALE + 0.5;
180   scaledFont.setPixelSize( pixelSize );
181   return scaledFont;
182 }
183 
fontAscentMM(const QFont & font)184 double QgsLayoutUtils::fontAscentMM( const QFont &font )
185 {
186   //upscale using FONT_WORKAROUND_SCALE
187   //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
188   QFont metricsFont = scaledFontPixelSize( font );
189   QFontMetricsF fontMetrics( metricsFont );
190   return ( fontMetrics.ascent() / FONT_WORKAROUND_SCALE );
191 }
192 
fontDescentMM(const QFont & font)193 double QgsLayoutUtils::fontDescentMM( const QFont &font )
194 {
195   //upscale using FONT_WORKAROUND_SCALE
196   //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
197   QFont metricsFont = scaledFontPixelSize( font );
198   QFontMetricsF fontMetrics( metricsFont );
199   return ( fontMetrics.descent() / FONT_WORKAROUND_SCALE );
200 
201 }
202 
fontHeightMM(const QFont & font)203 double QgsLayoutUtils::fontHeightMM( const QFont &font )
204 {
205   //upscale using FONT_WORKAROUND_SCALE
206   //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
207   QFont metricsFont = scaledFontPixelSize( font );
208   QFontMetricsF fontMetrics( metricsFont );
209   return ( fontMetrics.height() / FONT_WORKAROUND_SCALE );
210 
211 }
212 
fontHeightCharacterMM(const QFont & font,QChar character)213 double QgsLayoutUtils::fontHeightCharacterMM( const QFont &font, QChar character )
214 {
215   //upscale using FONT_WORKAROUND_SCALE
216   //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
217   QFont metricsFont = scaledFontPixelSize( font );
218   QFontMetricsF fontMetrics( metricsFont );
219   return ( fontMetrics.boundingRect( character ).height() / FONT_WORKAROUND_SCALE );
220 }
221 
textWidthMM(const QFont & font,const QString & text)222 double QgsLayoutUtils::textWidthMM( const QFont &font, const QString &text )
223 {
224   //upscale using FONT_WORKAROUND_SCALE
225   //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
226 
227   const QStringList multiLineSplit = text.split( '\n' );
228   QFont metricsFont = scaledFontPixelSize( font );
229   QFontMetricsF fontMetrics( metricsFont );
230 
231   double maxWidth = 0;
232   for ( const QString &line : multiLineSplit )
233   {
234 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
235     maxWidth = std::max( maxWidth, ( fontMetrics.width( line ) / FONT_WORKAROUND_SCALE ) );
236 #else
237     maxWidth = std::max( maxWidth, ( fontMetrics.horizontalAdvance( line ) / FONT_WORKAROUND_SCALE ) );
238 #endif
239   }
240   return maxWidth;
241 }
242 
textHeightMM(const QFont & font,const QString & text,double multiLineHeight)243 double QgsLayoutUtils::textHeightMM( const QFont &font, const QString &text, double multiLineHeight )
244 {
245   QStringList multiLineSplit = text.split( '\n' );
246   int lines = multiLineSplit.size();
247 
248   //upscale using FONT_WORKAROUND_SCALE
249   //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
250   QFont metricsFont = scaledFontPixelSize( font );
251   QFontMetricsF fontMetrics( metricsFont );
252 
253   double fontHeight = fontMetrics.ascent() + fontMetrics.descent(); // ignore +1 for baseline
254   double textHeight = fontMetrics.ascent() + static_cast< double >( ( lines - 1 ) * fontHeight * multiLineHeight );
255 
256   return textHeight / FONT_WORKAROUND_SCALE;
257 }
258 
drawText(QPainter * painter,QPointF position,const QString & text,const QFont & font,const QColor & color)259 void QgsLayoutUtils::drawText( QPainter *painter, QPointF position, const QString &text, const QFont &font, const QColor &color )
260 {
261   if ( !painter )
262   {
263     return;
264   }
265 
266   //upscale using FONT_WORKAROUND_SCALE
267   //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
268   QFont textFont = scaledFontPixelSize( font );
269 
270   QgsScopedQPainterState painterState( painter );
271   painter->setFont( textFont );
272   if ( color.isValid() )
273   {
274     painter->setPen( color );
275   }
276   double scaleFactor = 1.0 / FONT_WORKAROUND_SCALE;
277   painter->scale( scaleFactor, scaleFactor );
278   painter->drawText( position * FONT_WORKAROUND_SCALE, text );
279 }
280 
drawText(QPainter * painter,const QRectF & rect,const QString & text,const QFont & font,const QColor & color,const Qt::AlignmentFlag halignment,const Qt::AlignmentFlag valignment,const int flags)281 void QgsLayoutUtils::drawText( QPainter *painter, const QRectF &rect, const QString &text, const QFont &font, const QColor &color, const Qt::AlignmentFlag halignment, const Qt::AlignmentFlag valignment, const int flags )
282 {
283   if ( !painter )
284   {
285     return;
286   }
287 
288   //upscale using FONT_WORKAROUND_SCALE
289   //ref: http://osgeo-org.1560.x6.nabble.com/Multi-line-labels-and-font-bug-td4157152.html
290   QFont textFont = scaledFontPixelSize( font );
291 
292   QRectF scaledRect( rect.x() * FONT_WORKAROUND_SCALE, rect.y() * FONT_WORKAROUND_SCALE,
293                      rect.width() * FONT_WORKAROUND_SCALE, rect.height() * FONT_WORKAROUND_SCALE );
294 
295   QgsScopedQPainterState painterState( painter );
296   painter->setFont( textFont );
297   if ( color.isValid() )
298   {
299     painter->setPen( color );
300   }
301   double scaleFactor = 1.0 / FONT_WORKAROUND_SCALE;
302   painter->scale( scaleFactor, scaleFactor );
303   painter->drawText( scaledRect, halignment | valignment | flags, text );
304 }
305 
largestRotatedRectWithinBounds(const QRectF & originalRect,const QRectF & boundsRect,const double rotation)306 QRectF QgsLayoutUtils::largestRotatedRectWithinBounds( const QRectF &originalRect, const QRectF &boundsRect, const double rotation )
307 {
308   double originalWidth = originalRect.width();
309   double originalHeight = originalRect.height();
310   double boundsWidth = boundsRect.width();
311   double boundsHeight = boundsRect.height();
312   double ratioBoundsRect = boundsWidth / boundsHeight;
313 
314   double clippedRotation = normalizedAngle( rotation );
315 
316   //shortcut for some rotation values
317   if ( qgsDoubleNear( clippedRotation, 0.0 ) || qgsDoubleNear( clippedRotation, 90.0 ) || qgsDoubleNear( clippedRotation, 180.0 ) || qgsDoubleNear( clippedRotation, 270.0 ) )
318   {
319     double rectScale;
320     if ( qgsDoubleNear( clippedRotation, 0.0 ) || qgsDoubleNear( clippedRotation, 180.0 ) )
321     {
322       rectScale = ( ( originalWidth / originalHeight ) > ratioBoundsRect ) ? boundsWidth / originalWidth : boundsHeight / originalHeight;
323     }
324     else
325     {
326       rectScale = ( ( originalHeight / originalWidth ) > ratioBoundsRect ) ? boundsWidth / originalHeight : boundsHeight / originalWidth;
327     }
328     double rectScaledWidth = rectScale * originalWidth;
329     double rectScaledHeight = rectScale * originalHeight;
330 
331     if ( qgsDoubleNear( clippedRotation, 0.0 ) || qgsDoubleNear( clippedRotation, 180.0 ) )
332     {
333       return QRectF( ( boundsWidth - rectScaledWidth ) / 2.0, ( boundsHeight - rectScaledHeight ) / 2.0, rectScaledWidth, rectScaledHeight );
334     }
335     else
336     {
337       return QRectF( ( boundsWidth - rectScaledHeight ) / 2.0, ( boundsHeight - rectScaledWidth ) / 2.0, rectScaledWidth, rectScaledHeight );
338     }
339   }
340 
341   //convert angle to radians and flip
342   double angleRad = -clippedRotation * M_DEG2RAD;
343   double cosAngle = std::cos( angleRad );
344   double sinAngle = std::sin( angleRad );
345 
346   //calculate size of bounds of rotated rectangle
347   double widthBoundsRotatedRect = originalWidth * std::fabs( cosAngle ) + originalHeight * std::fabs( sinAngle );
348   double heightBoundsRotatedRect = originalHeight * std::fabs( cosAngle ) + originalWidth * std::fabs( sinAngle );
349 
350   //compare ratio of rotated rect with bounds rect and calculate scaling of rotated
351   //rect to fit within bounds
352   double ratioBoundsRotatedRect = widthBoundsRotatedRect / heightBoundsRotatedRect;
353   double rectScale = ratioBoundsRotatedRect > ratioBoundsRect ? boundsWidth / widthBoundsRotatedRect : boundsHeight / heightBoundsRotatedRect;
354   double rectScaledWidth = rectScale * originalWidth;
355   double rectScaledHeight = rectScale * originalHeight;
356 
357   //now calculate offset so that rotated rectangle is centered within bounds
358   //first calculate min x and y coordinates
359   double currentCornerX = 0;
360   double minX = 0;
361   currentCornerX += rectScaledWidth * cosAngle;
362   minX = minX < currentCornerX ? minX : currentCornerX;
363   currentCornerX += rectScaledHeight * sinAngle;
364   minX = minX < currentCornerX ? minX : currentCornerX;
365   currentCornerX -= rectScaledWidth * cosAngle;
366   minX = minX < currentCornerX ? minX : currentCornerX;
367 
368   double currentCornerY = 0;
369   double minY = 0;
370   currentCornerY -= rectScaledWidth * sinAngle;
371   minY = minY < currentCornerY ? minY : currentCornerY;
372   currentCornerY += rectScaledHeight * cosAngle;
373   minY = minY < currentCornerY ? minY : currentCornerY;
374   currentCornerY += rectScaledWidth * sinAngle;
375   minY = minY < currentCornerY ? minY : currentCornerY;
376 
377   //now calculate offset position of rotated rectangle
378   double offsetX = ratioBoundsRotatedRect > ratioBoundsRect ? 0 : ( boundsWidth - rectScale * widthBoundsRotatedRect ) / 2.0;
379   offsetX += std::fabs( minX );
380   double offsetY = ratioBoundsRotatedRect > ratioBoundsRect ? ( boundsHeight - rectScale * heightBoundsRotatedRect ) / 2.0 : 0;
381   offsetY += std::fabs( minY );
382 
383   return QRectF( offsetX, offsetY, rectScaledWidth, rectScaledHeight );
384 }
385 
decodePaperOrientation(const QString & string,bool & ok)386 QgsLayoutItemPage::Orientation QgsLayoutUtils::decodePaperOrientation( const QString &string, bool &ok )
387 {
388   QString s = string.trimmed();
389   if ( s.compare( QLatin1String( "Portrait" ), Qt::CaseInsensitive ) == 0 )
390   {
391     ok = true;
392     return QgsLayoutItemPage::Portrait;
393   }
394   else if ( s.compare( QLatin1String( "Landscape" ), Qt::CaseInsensitive ) == 0 )
395   {
396     ok = true;
397     return QgsLayoutItemPage::Landscape;
398   }
399   ok = false;
400   return QgsLayoutItemPage::Landscape; // default to landscape
401 }
402 
scaleFactorFromItemStyle(const QStyleOptionGraphicsItem * style)403 double QgsLayoutUtils::scaleFactorFromItemStyle( const QStyleOptionGraphicsItem *style )
404 {
405   // workaround Qt bug 66185
406 
407   // Refs #18027 - if a QGraphicsItem is rotated by 90 or 270 degrees, then the item
408   // style given to QGraphicsItem::paint incorrectly uses the shear parameter of the matrix (m12)
409   // to store the current view scale, instead of the horizontal scale parameter (m11) which
410   // is used in all other cases
411 
412   // TODO - ifdef this out if Qt fixes upstream
413   return !qgsDoubleNear( style->matrix.m11(), 0.0 ) ? style->matrix.m11() : style->matrix.m12();
414 }
415 
mapLayerFromString(const QString & string,QgsProject * project)416 QgsMapLayer *QgsLayoutUtils::mapLayerFromString( const QString &string, QgsProject *project )
417 {
418   // Maybe it's a layer id?
419   if ( QgsMapLayer *ml = project->mapLayer( string ) )
420     return ml;
421 
422   // Still nothing? Check for layer name
423   if ( QgsMapLayer *ml = project->mapLayersByName( string ).value( 0 ) )
424     return ml;
425 
426   // Still nothing? Check for layer name, case-insensitive
427   const auto layers = project->mapLayers();
428   for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
429   {
430     if ( it.value()->name().compare( string, Qt::CaseInsensitive ) == 0 )
431       return it.value();
432   }
433 
434   return nullptr;
435 }
436 
437 // nextNiceNumber(4573.23, d) = 5000 (d=1) -> 4600 (d=10) -> 4580 (d=100) -> 4574 (d=1000) -> etc
nextNiceNumber(double a,double d=1)438 inline double nextNiceNumber( double a, double d = 1 )
439 {
440   double s = std::pow( 10.0, std::floor( std::log10( a ) ) ) / d;
441   return std::ceil( a / s ) * s;
442 }
443 
444 // prevNiceNumber(4573.23, d) = 4000 (d=1) -> 4500 (d=10) -> 4570 (d=100) -> 4573 (d=1000) -> etc
prevNiceNumber(double a,double d=1)445 inline double prevNiceNumber( double a, double d = 1 )
446 {
447   double s = std::pow( 10.0, std::floor( std::log10( a ) ) ) / d;
448   return std::floor( a / s ) * s;
449 }
450 
calculatePrettySize(const double minimumSize,const double maximumSize)451 double QgsLayoutUtils::calculatePrettySize( const double minimumSize, const double maximumSize )
452 {
453   if ( maximumSize < minimumSize )
454   {
455     return 0;
456   }
457   else
458   {
459     // Start with coarsest "nice" number closest to minimumSize resp
460     // maximumSize, then proceed to finer numbers as long as neither
461     // lowerNiceUnitsPerSeg nor upperNiceUnitsPerSeg are in
462     // [minimumSize, maximumSize]
463     double lowerNiceUnitsPerSeg = nextNiceNumber( minimumSize );
464     double upperNiceUnitsPerSeg = prevNiceNumber( maximumSize );
465 
466     double d = 1;
467     while ( lowerNiceUnitsPerSeg > maximumSize && upperNiceUnitsPerSeg < minimumSize )
468     {
469       d *= 10;
470       lowerNiceUnitsPerSeg = nextNiceNumber( minimumSize, d );
471       upperNiceUnitsPerSeg = prevNiceNumber( maximumSize, d );
472     }
473 
474     // Pick size from {lowerNiceUnitsPerSeg, upperNiceUnitsPerSeg}, use the larger if possible
475     return upperNiceUnitsPerSeg < minimumSize ? lowerNiceUnitsPerSeg : upperNiceUnitsPerSeg;
476   }
477 }
478 
itemIsAClippingSource(const QgsLayoutItem * item)479 bool QgsLayoutUtils::itemIsAClippingSource( const QgsLayoutItem *item )
480 {
481   if ( !( item->itemFlags() & QgsLayoutItem::FlagProvidesClipPath ) )
482     return false; // not a clipping provider, so shortcut out
483 
484   // current only maps can be clipped
485   QList< QgsLayoutItemMap * > maps;
486   item->layout()->layoutItems( maps );
487   for ( QgsLayoutItemMap *map : qgis::as_const( maps ) )
488   {
489     if ( map->itemClippingSettings()->isActive() && map->itemClippingSettings()->sourceItem() == item )
490       return true;
491   }
492   return false;
493 }
494 
pointsToMM(const double pointSize)495 double QgsLayoutUtils::pointsToMM( const double pointSize )
496 {
497   //conversion to mm based on 1 point = 1/72 inch
498   return ( pointSize * 0.3527 );
499 }
500 
mmToPoints(const double mmSize)501 double QgsLayoutUtils::mmToPoints( const double mmSize )
502 {
503   //conversion to points based on 1 point = 1/72 inch
504   return ( mmSize / 0.3527 );
505 }
506 
predefinedScales(const QgsLayout * layout)507 QVector< double > QgsLayoutUtils::predefinedScales( const QgsLayout *layout )
508 {
509   QgsProject *lProject = layout ? layout->project() : nullptr;
510   QVector< double > mapScales;
511   if ( lProject )
512     mapScales = lProject->viewSettings()->mapScales();
513 
514   bool hasProjectScales( lProject ? lProject->viewSettings()->useProjectScales() : false );
515   if ( !hasProjectScales || mapScales.isEmpty() )
516   {
517     // default to global map tool scales
518     QgsSettings settings;
519     QString scalesStr( settings.value( QStringLiteral( "Map/scales" ), Qgis::defaultProjectScales() ).toString() );
520     const QStringList scales = scalesStr.split( ',' );
521     for ( const QString &scale : scales )
522     {
523       QStringList parts( scale.split( ':' ) );
524       if ( parts.size() == 2 )
525       {
526         mapScales.push_back( parts[1].toDouble() );
527       }
528     }
529   }
530 
531   return mapScales;
532 }
533