1 /***************************************************************************
2   testqgslabelingengine.cpp
3   --------------------------------------
4   Date                 : September 2015
5   Copyright            : (C) 2015 by Martin Dobias
6   Email                : wonder dot sk at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgstest.h"
17 
18 #include <qgsapplication.h>
19 #include <qgslabelingengine.h>
20 #include <qgsproject.h>
21 #include <qgsmaprenderersequentialjob.h>
22 #include <qgsreadwritecontext.h>
23 #include <qgsrulebasedlabeling.h>
24 #include <qgsvectorlayer.h>
25 #include <qgsvectorlayerdiagramprovider.h>
26 #include <qgsvectorlayerlabeling.h>
27 #include <qgsvectorlayerlabelprovider.h>
28 #include "qgsmultirenderchecker.h"
29 #include "qgsfontutils.h"
30 #include "qgsnullsymbolrenderer.h"
31 #include "qgssinglesymbolrenderer.h"
32 #include "qgssymbol.h"
33 #include "pointset.h"
34 
35 class TestQgsLabelingEngine : public QObject
36 {
37     Q_OBJECT
38   public:
39     TestQgsLabelingEngine() = default;
40 
41   private slots:
42     void initTestCase();
43     void cleanupTestCase();
44     void init();// will be called before each testfunction is executed.
45     void cleanup();// will be called after every testfunction.
46     void testEngineSettings();
47     void testBasic();
48     void testDiagrams();
49     void testRuleBased();
50     void zOrder(); //test that labels are stacked correctly
51     void testEncodeDecodePositionOrder();
52     void testEncodeDecodeLinePlacement();
53     void testSubstitutions();
54     void testCapitalization();
55     void testNumberFormat();
56     void testParticipatingLayers();
57     void testRegisterFeatureUnprojectible();
58     void testRotateHidePartial();
59     void testParallelLabelSmallFeature();
60     void testAdjacentParts();
61     void testTouchingParts();
62     void testMergingLinesWithForks();
63     void testCurvedLabelsWithTinySegments();
64     void testCurvedLabelCorrectLinePlacement();
65     void testCurvedLabelNegativeDistance();
66     void testCurvedLabelOnSmallLineNearCenter();
67     void testRepeatDistanceWithSmallLine();
68     void testParallelPlacementPreferAbove();
69     void testLabelBoundary();
70     void testLabelBlockingRegion();
71     void testLabelRotationWithReprojection();
72     void drawUnplaced();
73     void labelingResults();
74     void pointsetExtend();
75     void curvedOverrun();
76     void parallelOverrun();
77     void testDataDefinedLabelAllParts();
78     void testVerticalOrientation();
79     void testVerticalOrientationLetterLineSpacing();
80     void testRotationBasedOrientationPoint();
81     void testRotationBasedOrientationLine();
82     void testMapUnitLetterSpacing();
83     void testMapUnitWordSpacing();
84     void testReferencedFields();
85     void testClipping();
86     void testLineAnchorParallel();
87     void testLineAnchorParallelConstraints();
88     void testLineAnchorCurved();
89     void testLineAnchorCurvedConstraints();
90     void testLineAnchorHorizontal();
91     void testLineAnchorHorizontalConstraints();
92     void testShowAllLabelsWhenALabelHasNoCandidates();
93 
94   private:
95     QgsVectorLayer *vl = nullptr;
96 
97     QString mReport;
98 
99     void setDefaultLabelParams( QgsPalLayerSettings &settings );
100     QgsLabelingEngineSettings createLabelEngineSettings();
101     bool imageCheck( const QString &testName, QImage &image, int mismatchCount );
102 
103 };
104 
initTestCase()105 void TestQgsLabelingEngine::initTestCase()
106 {
107   mReport += QLatin1String( "<h1>Labeling Engine Tests</h1>\n" );
108 
109   QgsApplication::init();
110   QgsApplication::initQgis();
111   QgsApplication::showSettings();
112   QgsFontUtils::loadStandardTestFonts( QStringList() << QStringLiteral( "Bold" ) );
113 }
114 
cleanupTestCase()115 void TestQgsLabelingEngine::cleanupTestCase()
116 {
117   QgsApplication::exitQgis();
118   QString myReportFile = QDir::tempPath() + "/qgistest.html";
119   QFile myFile( myReportFile );
120   if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
121   {
122     QTextStream myQTextStream( &myFile );
123     myQTextStream << mReport;
124     myFile.close();
125   }
126 }
127 
init()128 void TestQgsLabelingEngine::init()
129 {
130   QString filename = QStringLiteral( TEST_DATA_DIR ) + "/points.shp";
131   vl = new QgsVectorLayer( filename, QStringLiteral( "points" ), QStringLiteral( "ogr" ) );
132   QVERIFY( vl->isValid() );
133   QgsProject::instance()->addMapLayer( vl );
134 }
135 
cleanup()136 void TestQgsLabelingEngine::cleanup()
137 {
138   QgsProject::instance()->removeMapLayer( vl->id() );
139   vl = nullptr;
140 }
141 
testEngineSettings()142 void TestQgsLabelingEngine::testEngineSettings()
143 {
144   // test labeling engine settings
145 
146   // getters/setters
147   QgsLabelingEngineSettings settings;
148 
149   // default for new projects should be placement engine v2
150   QCOMPARE( settings.placementVersion(), QgsLabelingEngineSettings::PlacementEngineVersion2 );
151 
152   settings.setDefaultTextRenderFormat( QgsRenderContext::TextFormatAlwaysText );
153   QCOMPARE( settings.defaultTextRenderFormat(), QgsRenderContext::TextFormatAlwaysText );
154   settings.setDefaultTextRenderFormat( QgsRenderContext::TextFormatAlwaysOutlines );
155   QCOMPARE( settings.defaultTextRenderFormat(), QgsRenderContext::TextFormatAlwaysOutlines );
156 
157   settings.setPlacementVersion( QgsLabelingEngineSettings::PlacementEngineVersion1 );
158   QCOMPARE( settings.placementVersion(), QgsLabelingEngineSettings::PlacementEngineVersion1 );
159 
160   settings.setFlag( QgsLabelingEngineSettings::DrawUnplacedLabels, true );
161   QVERIFY( settings.testFlag( QgsLabelingEngineSettings::DrawUnplacedLabels ) );
162   settings.setFlag( QgsLabelingEngineSettings::DrawUnplacedLabels, false );
163   QVERIFY( !settings.testFlag( QgsLabelingEngineSettings::DrawUnplacedLabels ) );
164 
165   settings.setUnplacedLabelColor( QColor( 0, 255, 0 ) );
166   QCOMPARE( settings.unplacedLabelColor().name(), QStringLiteral( "#00ff00" ) );
167 
168   // reading from project
169   QgsProject p;
170   settings.setDefaultTextRenderFormat( QgsRenderContext::TextFormatAlwaysText );
171   settings.setFlag( QgsLabelingEngineSettings::DrawUnplacedLabels, true );
172   settings.setUnplacedLabelColor( QColor( 0, 255, 0 ) );
173   settings.setPlacementVersion( QgsLabelingEngineSettings::PlacementEngineVersion1 );
174   settings.writeSettingsToProject( &p );
175   QgsLabelingEngineSettings settings2;
176   settings2.readSettingsFromProject( &p );
177   QCOMPARE( settings2.defaultTextRenderFormat(), QgsRenderContext::TextFormatAlwaysText );
178   QVERIFY( settings2.testFlag( QgsLabelingEngineSettings::DrawUnplacedLabels ) );
179   QCOMPARE( settings2.unplacedLabelColor().name(), QStringLiteral( "#00ff00" ) );
180 
181   settings.setDefaultTextRenderFormat( QgsRenderContext::TextFormatAlwaysOutlines );
182   settings.setFlag( QgsLabelingEngineSettings::DrawUnplacedLabels, false );
183   settings.writeSettingsToProject( &p );
184   settings2.readSettingsFromProject( &p );
185   QCOMPARE( settings2.defaultTextRenderFormat(), QgsRenderContext::TextFormatAlwaysOutlines );
186   QVERIFY( !settings2.testFlag( QgsLabelingEngineSettings::DrawUnplacedLabels ) );
187   QCOMPARE( settings2.placementVersion(), QgsLabelingEngineSettings::PlacementEngineVersion1 );
188 
189   // test that older setting is still respected as a fallback
190   QgsProject p2;
191   QgsLabelingEngineSettings settings3;
192   p2.writeEntry( QStringLiteral( "PAL" ), QStringLiteral( "/DrawOutlineLabels" ), false );
193   settings3.readSettingsFromProject( &p2 );
194   QCOMPARE( settings3.defaultTextRenderFormat(), QgsRenderContext::TextFormatAlwaysText );
195 
196   p2.writeEntry( QStringLiteral( "PAL" ), QStringLiteral( "/DrawOutlineLabels" ), true );
197   settings3.readSettingsFromProject( &p2 );
198   QCOMPARE( settings3.defaultTextRenderFormat(), QgsRenderContext::TextFormatAlwaysOutlines );
199 
200   // when opening an older project, labeling engine version should be 1
201   p2.removeEntry( QStringLiteral( "PAL" ), QStringLiteral( "/PlacementEngineVersion" ) );
202   settings3.readSettingsFromProject( &p2 );
203   QCOMPARE( settings3.placementVersion(), QgsLabelingEngineSettings::PlacementEngineVersion1 );
204 }
205 
setDefaultLabelParams(QgsPalLayerSettings & settings)206 void TestQgsLabelingEngine::setDefaultLabelParams( QgsPalLayerSettings &settings )
207 {
208   QgsTextFormat format;
209   format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
210   format.setSize( 12 );
211   format.setNamedStyle( QStringLiteral( "Bold" ) );
212   format.setColor( QColor( 200, 0, 200 ) );
213   settings.setFormat( format );
214 }
215 
createLabelEngineSettings()216 QgsLabelingEngineSettings TestQgsLabelingEngine::createLabelEngineSettings()
217 {
218   QgsLabelingEngineSettings settings;
219   settings.setPlacementVersion( QgsLabelingEngineSettings::PlacementEngineVersion2 );
220   return settings;
221 }
222 
testBasic()223 void TestQgsLabelingEngine::testBasic()
224 {
225   QSize size( 640, 480 );
226   QgsMapSettings mapSettings;
227   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
228   mapSettings.setOutputSize( size );
229   mapSettings.setExtent( vl->extent() );
230   mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
231   mapSettings.setOutputDpi( 96 );
232 
233   // first render the map and labeling separately
234 
235   QgsMapRendererSequentialJob job( mapSettings );
236   job.start();
237   job.waitForFinished();
238 
239   QImage img = job.renderedImage();
240 
241   QPainter p( &img );
242   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
243   context.setPainter( &p );
244 
245   QgsPalLayerSettings settings;
246   settings.fieldName = QStringLiteral( "Class" );
247   setDefaultLabelParams( settings );
248 
249   vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
250   vl->setLabelsEnabled( true );
251 
252   QgsDefaultLabelingEngine engine;
253   engine.setMapSettings( mapSettings );
254   engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
255   //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
256   engine.run( context );
257 
258   p.end();
259 
260   QVERIFY( imageCheck( "labeling_basic", img, 20 ) );
261 
262   // now let's test the variant when integrated into rendering loop
263   //note the reference images are slightly different due to use of renderer for this test
264 
265   job.start();
266   job.waitForFinished();
267   QImage img2 = job.renderedImage();
268 
269   vl->setLabeling( nullptr );
270 
271   QVERIFY( imageCheck( "labeling_basic", img2, 20 ) );
272 }
273 
274 
testDiagrams()275 void TestQgsLabelingEngine::testDiagrams()
276 {
277   QSize size( 640, 480 );
278   QgsMapSettings mapSettings;
279   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
280   mapSettings.setOutputSize( size );
281   mapSettings.setExtent( vl->extent() );
282   mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
283   mapSettings.setOutputDpi( 96 );
284 
285   // first render the map and diagrams separately
286 
287   QgsMapRendererSequentialJob job( mapSettings );
288   job.start();
289   job.waitForFinished();
290 
291   QImage img = job.renderedImage();
292 
293   QPainter p( &img );
294   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
295   context.setPainter( &p );
296 
297   bool res;
298   vl->loadNamedStyle( QStringLiteral( TEST_DATA_DIR ) + "/points_diagrams.qml", res );
299   QVERIFY( res );
300 
301   QgsDefaultLabelingEngine engine;
302   engine.setMapSettings( mapSettings );
303   engine.addProvider( new QgsVectorLayerDiagramProvider( vl ) );
304   engine.run( context );
305 
306   p.end();
307 
308   QVERIFY( imageCheck( "labeling_point_diagrams", img, 20 ) );
309 
310   // now let's test the variant when integrated into rendering loop
311   job.start();
312   job.waitForFinished();
313   QImage img2 = job.renderedImage();
314 
315   vl->loadDefaultStyle( res );
316   QVERIFY( imageCheck( "labeling_point_diagrams", img2, 20 ) );
317 }
318 
319 
testRuleBased()320 void TestQgsLabelingEngine::testRuleBased()
321 {
322   QSize size( 640, 480 );
323   QgsMapSettings mapSettings;
324   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
325   mapSettings.setOutputSize( size );
326   mapSettings.setExtent( vl->extent() );
327   mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
328   mapSettings.setOutputDpi( 96 );
329 
330   // set up most basic rule-based labeling for layer
331   QgsRuleBasedLabeling::Rule *root = new QgsRuleBasedLabeling::Rule( nullptr );
332 
333   QgsPalLayerSettings s1;
334   s1.fieldName = QStringLiteral( "Class" );
335   s1.obstacleSettings().setIsObstacle( false );
336   s1.dist = 2;
337   QgsTextFormat format = s1.format();
338   format.setColor( QColor( 200, 0, 200 ) );
339   format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) );
340   format.setSize( 12 );
341   s1.setFormat( format );
342   s1.placement = QgsPalLayerSettings::OverPoint;
343   s1.quadOffset = QgsPalLayerSettings::QuadrantAboveLeft;
344   s1.displayAll = true;
345 
346   root->appendChild( new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings( s1 ) ) );
347 
348   QgsPalLayerSettings s2;
349   s2.fieldName = QStringLiteral( "Class" );
350   s2.obstacleSettings().setIsObstacle( false );
351   s2.dist = 2;
352   format = s2.format();
353   format.setColor( Qt::red );
354   format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) );
355   s2.setFormat( format );
356   s2.placement = QgsPalLayerSettings::OverPoint;
357   s2.quadOffset = QgsPalLayerSettings::QuadrantBelowRight;
358   s2.displayAll = true;
359 
360   s2.dataDefinedProperties().setProperty( QgsPalLayerSettings::Size, QgsProperty::fromValue( QStringLiteral( "18" ) ) );
361 
362   root->appendChild( new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings( s2 ), 0, 0, QStringLiteral( "Class = 'Jet'" ) ) );
363 
364   vl->setLabeling( new QgsRuleBasedLabeling( root ) );
365   vl->setLabelsEnabled( true );
366   //setDefaultLabelParams( vl );
367 
368   QgsMapRendererSequentialJob job( mapSettings );
369   job.start();
370   job.waitForFinished();
371   QImage img = job.renderedImage();
372   QVERIFY( imageCheck( "labeling_rulebased", img, 20 ) );
373 
374   // test read/write rules
375   QDomDocument doc, doc2, doc3;
376   QDomElement e = vl->labeling()->save( doc, QgsReadWriteContext() );
377   doc.appendChild( e );
378   // read saved rules
379   doc2.setContent( doc.toString() );
380   QDomElement e2 = doc2.documentElement();
381   QgsRuleBasedLabeling *rl2 = QgsRuleBasedLabeling::create( e2, QgsReadWriteContext() );
382   QVERIFY( rl2 );
383   // check that another save will keep the data the same
384   QDomElement e3 = rl2->save( doc3, QgsReadWriteContext() );
385   doc3.appendChild( e3 );
386   QCOMPARE( doc.toString(), doc3.toString() );
387 
388   vl->setLabeling( nullptr );
389 
390   delete rl2;
391 
392 #if 0
393   QPainter p( &img );
394   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
395   context.setPainter( &p );
396 
397   QgsLabelingEngine engine;
398   engine.setMapSettings( mapSettings );
399   engine.addProvider( new QgsRuleBasedLabelProvider(, vl ) );
400   engine.run( context );
401 #endif
402 }
403 
zOrder()404 void TestQgsLabelingEngine::zOrder()
405 {
406   QSize size( 640, 480 );
407   QgsMapSettings mapSettings;
408   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
409   mapSettings.setOutputSize( size );
410   mapSettings.setExtent( vl->extent() );
411   mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
412   mapSettings.setOutputDpi( 96 );
413 
414   QgsMapRendererSequentialJob job( mapSettings );
415   job.start();
416   job.waitForFinished();
417 
418   QImage img = job.renderedImage();
419 
420   QPainter p( &img );
421   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
422   context.setPainter( &p );
423 
424   QgsPalLayerSettings pls1;
425   pls1.fieldName = QStringLiteral( "Class" );
426   pls1.placement = QgsPalLayerSettings::OverPoint;
427   pls1.quadOffset = QgsPalLayerSettings::QuadrantAboveRight;
428   pls1.displayAll = true;
429   QgsTextFormat format = pls1.format();
430   format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) );
431   format.setSize( 70 );
432   pls1.setFormat( format );
433 
434   //use data defined coloring and font size so that stacking order of labels can be determined
435   pls1.dataDefinedProperties().setProperty( QgsPalLayerSettings::Color, QgsProperty::fromExpression( QStringLiteral( "case when \"Class\"='Jet' then '#ff5500' when \"Class\"='B52' then '#00ffff' else '#ff00ff' end" ) ) );
436   pls1.dataDefinedProperties().setProperty( QgsPalLayerSettings::Size, QgsProperty::fromExpression( QStringLiteral( "case when \"Class\"='Jet' then 100 when \"Class\"='B52' then 30 else 50 end" ) ) );
437 
438   QgsVectorLayerLabelProvider *provider1 = new QgsVectorLayerLabelProvider( vl, QString(), true, &pls1 );
439   QgsDefaultLabelingEngine engine;
440   engine.setMapSettings( mapSettings );
441   engine.addProvider( provider1 );
442   //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
443   engine.run( context );
444   p.end();
445   engine.removeProvider( provider1 );
446 
447   // since labels are all from same layer and have same z-index then smaller labels should be stacked on top of larger
448   // labels. For example: B52 > Biplane > Jet
449   QVERIFY( imageCheck( "label_order_size", img, 20 ) );
450   img = job.renderedImage();
451 
452   //test data defined z-index
453   pls1.dataDefinedProperties().setProperty( QgsPalLayerSettings::ZIndex, QgsProperty::fromExpression( QStringLiteral( "case when \"Class\"='Jet' then 3 when \"Class\"='B52' then 1 else 2 end" ) ) );
454   provider1 = new QgsVectorLayerLabelProvider( vl, QString(), true, &pls1 );
455   engine.addProvider( provider1 );
456   p.begin( &img );
457   engine.run( context );
458   p.end();
459   engine.removeProvider( provider1 );
460 
461   // z-index will take preference over label size, so labels should be stacked Jet > Biplane > B52
462   QVERIFY( imageCheck( "label_order_zindex", img, 20 ) );
463   img = job.renderedImage();
464 
465   pls1.dataDefinedProperties().clear();
466   format = pls1.format();
467   format.setColor( QColor( 255, 50, 100 ) );
468   format.setSize( 30 );
469   pls1.setFormat( format );
470   provider1 = new QgsVectorLayerLabelProvider( vl, QString(), true, &pls1 );
471   engine.addProvider( provider1 );
472 
473   //add a second layer
474   QString filename = QStringLiteral( TEST_DATA_DIR ) + "/points.shp";
475   QgsVectorLayer *vl2 = new QgsVectorLayer( filename, QStringLiteral( "points" ), QStringLiteral( "ogr" ) );
476   QVERIFY( vl2->isValid() );
477   QgsProject::instance()->addMapLayer( vl2 );
478 
479   QgsPalLayerSettings pls2( pls1 );
480   format = pls2.format();
481   format.setColor( QColor( 0, 0, 0 ) );
482   pls2.setFormat( format );
483   QgsVectorLayerLabelProvider *provider2 = new QgsVectorLayerLabelProvider( vl2, QString(), true, &pls2 );
484   engine.addProvider( provider2 );
485 
486   mapSettings.setLayers( QList<QgsMapLayer *>() << vl << vl2 );
487   engine.setMapSettings( mapSettings );
488 
489   p.begin( &img );
490   engine.run( context );
491   p.end();
492 
493   // labels have same z-index, so layer order will be used
494   QVERIFY( imageCheck( "label_order_layer1", img, 20 ) );
495   img = job.renderedImage();
496 
497   //flip layer order and re-test
498   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2 << vl );
499   engine.setMapSettings( mapSettings );
500   p.begin( &img );
501   engine.run( context );
502   p.end();
503 
504   // label order should be reversed
505   QVERIFY( imageCheck( "label_order_layer2", img, 20 ) );
506   img = job.renderedImage();
507 
508   //try mixing layer order and z-index
509   engine.removeProvider( provider1 );
510   pls1.dataDefinedProperties().setProperty( QgsPalLayerSettings::ZIndex, QgsProperty::fromExpression( QStringLiteral( "if(\"Class\"='Jet',3,0)" ) ) );
511   provider1 = new QgsVectorLayerLabelProvider( vl, QString(), true, &pls1 );
512   engine.addProvider( provider1 );
513 
514   p.begin( &img );
515   engine.run( context );
516   p.end();
517 
518   // label order should be most labels from layer 1, then labels from layer 2, then "Jet"s from layer 1
519   QVERIFY( imageCheck( "label_order_mixed", img, 20 ) );
520   img = job.renderedImage();
521 
522   //cleanup
523   QgsProject::instance()->removeMapLayer( vl2 );
524 }
525 
testEncodeDecodePositionOrder()526 void TestQgsLabelingEngine::testEncodeDecodePositionOrder()
527 {
528   //create an ordered position list
529   QVector< QgsPalLayerSettings::PredefinedPointPosition > original;
530   //make sure all placements are added here
531   original << QgsPalLayerSettings::BottomLeft << QgsPalLayerSettings::BottomSlightlyLeft
532            << QgsPalLayerSettings::BottomMiddle << QgsPalLayerSettings::BottomSlightlyRight
533            << QgsPalLayerSettings::BottomRight << QgsPalLayerSettings::MiddleRight
534            << QgsPalLayerSettings::MiddleLeft << QgsPalLayerSettings::TopLeft
535            << QgsPalLayerSettings::TopSlightlyLeft << QgsPalLayerSettings::TopMiddle
536            << QgsPalLayerSettings::TopSlightlyRight << QgsPalLayerSettings::TopRight;
537   //encode list
538   QString encoded = QgsLabelingUtils::encodePredefinedPositionOrder( original );
539   QVERIFY( !encoded.isEmpty() );
540 
541   //decode
542   QVector< QgsPalLayerSettings::PredefinedPointPosition > decoded = QgsLabelingUtils::decodePredefinedPositionOrder( encoded );
543   QCOMPARE( decoded, original );
544 
545   //test decoding with a messy string
546   decoded = QgsLabelingUtils::decodePredefinedPositionOrder( QStringLiteral( ",tr,x,BSR, L, t,," ) );
547   QVector< QgsPalLayerSettings::PredefinedPointPosition > expected;
548   expected << QgsPalLayerSettings::TopRight << QgsPalLayerSettings::BottomSlightlyRight
549            << QgsPalLayerSettings::MiddleLeft << QgsPalLayerSettings::TopMiddle;
550   QCOMPARE( decoded, expected );
551 }
552 
testEncodeDecodeLinePlacement()553 void TestQgsLabelingEngine::testEncodeDecodeLinePlacement()
554 {
555   QString encoded = QgsLabelingUtils::encodeLinePlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine | QgsLabeling::LinePlacementFlag::OnLine );
556   QVERIFY( !encoded.isEmpty() );
557   QCOMPARE( QgsLabelingUtils::decodeLinePlacementFlags( encoded ), QgsLabeling::LinePlacementFlag::AboveLine | QgsLabeling::LinePlacementFlag::OnLine );
558   encoded = QgsLabelingUtils::encodeLinePlacementFlags( QgsLabeling::LinePlacementFlag::OnLine | QgsLabeling::LinePlacementFlag::MapOrientation );
559   QVERIFY( !encoded.isEmpty() );
560   QCOMPARE( QgsLabelingUtils::decodeLinePlacementFlags( encoded ), QgsLabeling::LinePlacementFlag::OnLine | QgsLabeling::LinePlacementFlag::MapOrientation );
561 
562   //test decoding with a messy string
563   QCOMPARE( QgsLabelingUtils::decodeLinePlacementFlags( QStringLiteral( ",ol,," ) ), QgsLabeling::LinePlacementFlag::OnLine | QgsLabeling::LinePlacementFlag::MapOrientation );
564   QCOMPARE( QgsLabelingUtils::decodeLinePlacementFlags( QStringLiteral( ",ol,BL,  al" ) ), QgsLabeling::LinePlacementFlag::OnLine | QgsLabeling::LinePlacementFlag::AboveLine | QgsLabeling::LinePlacementFlag::BelowLine | QgsLabeling::LinePlacementFlag::MapOrientation );
565   QCOMPARE( QgsLabelingUtils::decodeLinePlacementFlags( QStringLiteral( ",ol,BL, LO,  al" ) ), QgsLabeling::LinePlacementFlag::OnLine | QgsLabeling::LinePlacementFlag::AboveLine | QgsLabeling::LinePlacementFlag::BelowLine );
566 }
567 
testSubstitutions()568 void TestQgsLabelingEngine::testSubstitutions()
569 {
570   QgsPalLayerSettings settings;
571   settings.useSubstitutions = false;
572   QgsStringReplacementCollection collection( QList< QgsStringReplacement >() << QgsStringReplacement( QStringLiteral( "aa" ), QStringLiteral( "bb" ) ) );
573   settings.substitutions = collection;
574   settings.fieldName = QStringLiteral( "'aa label'" );
575   settings.isExpression = true;
576 
577   QgsVectorLayerLabelProvider *provider = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test" ), true, &settings );
578   QgsFeature f( vl->fields(), 1 );
579   f.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( -100, 30 ) ) );
580 
581   // make a fake render context
582   QSize size( 640, 480 );
583   QgsMapSettings mapSettings;
584   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
585   mapSettings.setOutputSize( size );
586   mapSettings.setExtent( vl->extent() );
587   mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
588   mapSettings.setOutputDpi( 96 );
589   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
590   QSet<QString> attributes;
591   QgsDefaultLabelingEngine engine;
592   engine.setMapSettings( mapSettings );
593   engine.addProvider( provider );
594   provider->prepare( context, attributes );
595 
596   provider->registerFeature( f, context );
597   QCOMPARE( provider->mLabels.at( 0 )->labelText(), QString( "aa label" ) );
598 
599   //with substitution
600   settings.useSubstitutions = true;
601   QgsVectorLayerLabelProvider *provider2 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test2" ), true, &settings );
602   engine.addProvider( provider2 );
603   provider2->prepare( context, attributes );
604 
605   provider2->registerFeature( f, context );
606   QCOMPARE( provider2->mLabels.at( 0 )->labelText(), QString( "bb label" ) );
607 }
608 
testCapitalization()609 void TestQgsLabelingEngine::testCapitalization()
610 {
611   QgsFeature f( vl->fields(), 1 );
612   f.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( -100, 30 ) ) );
613 
614   // make a fake render context
615   QSize size( 640, 480 );
616   QgsMapSettings mapSettings;
617   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
618   mapSettings.setOutputSize( size );
619   mapSettings.setExtent( vl->extent() );
620   mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
621   mapSettings.setOutputDpi( 96 );
622   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
623   QSet<QString> attributes;
624   QgsDefaultLabelingEngine engine;
625   engine.setMapSettings( mapSettings );
626 
627   // no change
628   QgsPalLayerSettings settings;
629   QgsTextFormat format = settings.format();
630   QFont font = format.font();
631   font.setCapitalization( QFont::MixedCase );
632   format.setFont( font );
633   settings.setFormat( format );
634   settings.fieldName = QStringLiteral( "'a teSt LABEL'" );
635   settings.isExpression = true;
636 
637   QgsVectorLayerLabelProvider *provider = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test" ), true, &settings );
638   engine.addProvider( provider );
639   provider->prepare( context, attributes );
640   provider->registerFeature( f, context );
641   QCOMPARE( provider->mLabels.at( 0 )->labelText(), QString( "a teSt LABEL" ) );
642 
643   //uppercase
644   font.setCapitalization( QFont::AllUppercase );
645   format.setFont( font );
646   settings.setFormat( format );
647   QgsVectorLayerLabelProvider *provider2 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test2" ), true, &settings );
648   engine.addProvider( provider2 );
649   provider2->prepare( context, attributes );
650   provider2->registerFeature( f, context );
651   QCOMPARE( provider2->mLabels.at( 0 )->labelText(), QString( "A TEST LABEL" ) );
652 
653   font.setCapitalization( QFont::MixedCase );
654   format.setCapitalization( QgsStringUtils::AllUppercase );
655   format.setFont( font );
656   settings.setFormat( format );
657   QgsVectorLayerLabelProvider *provider2b = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test2" ), true, &settings );
658   engine.addProvider( provider2b );
659   provider2b->prepare( context, attributes );
660   provider2b->registerFeature( f, context );
661   QCOMPARE( provider2b->mLabels.at( 0 )->labelText(), QString( "A TEST LABEL" ) );
662 
663   //lowercase
664   font.setCapitalization( QFont::AllLowercase );
665   format.setCapitalization( QgsStringUtils::MixedCase );
666   format.setFont( font );
667   settings.setFormat( format );
668   QgsVectorLayerLabelProvider *provider3 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test3" ), true, &settings );
669   engine.addProvider( provider3 );
670   provider3->prepare( context, attributes );
671   provider3->registerFeature( f, context );
672   QCOMPARE( provider3->mLabels.at( 0 )->labelText(), QString( "a test label" ) );
673 
674   font.setCapitalization( QFont::MixedCase );
675   format.setCapitalization( QgsStringUtils::AllLowercase );
676   format.setFont( font );
677   settings.setFormat( format );
678   QgsVectorLayerLabelProvider *provider3b = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test3" ), true, &settings );
679   engine.addProvider( provider3b );
680   provider3b->prepare( context, attributes );
681   provider3b->registerFeature( f, context );
682   QCOMPARE( provider3b->mLabels.at( 0 )->labelText(), QString( "a test label" ) );
683 
684   //first letter uppercase
685   font.setCapitalization( QFont::Capitalize );
686   format.setCapitalization( QgsStringUtils::MixedCase );
687   format.setFont( font );
688   settings.setFormat( format );
689   QgsVectorLayerLabelProvider *provider4 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test4" ), true, &settings );
690   engine.addProvider( provider4 );
691   provider4->prepare( context, attributes );
692   provider4->registerFeature( f, context );
693   QCOMPARE( provider4->mLabels.at( 0 )->labelText(), QString( "A TeSt LABEL" ) );
694 
695   font.setCapitalization( QFont::MixedCase );
696   format.setCapitalization( QgsStringUtils::ForceFirstLetterToCapital );
697   format.setFont( font );
698   settings.setFormat( format );
699   QgsVectorLayerLabelProvider *provider4b = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test4" ), true, &settings );
700   engine.addProvider( provider4b );
701   provider4b->prepare( context, attributes );
702   provider4b->registerFeature( f, context );
703   QCOMPARE( provider4b->mLabels.at( 0 )->labelText(), QString( "A TeSt LABEL" ) );
704 
705   settings.fieldName = QStringLiteral( "'A TEST LABEL'" );
706   format.setCapitalization( QgsStringUtils::TitleCase );
707   format.setFont( font );
708   settings.setFormat( format );
709   QgsVectorLayerLabelProvider *provider5 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test4" ), true, &settings );
710   engine.addProvider( provider5 );
711   provider5->prepare( context, attributes );
712   provider5->registerFeature( f, context );
713   QCOMPARE( provider5->mLabels.at( 0 )->labelText(), QString( "A Test Label" ) );
714 }
715 
testNumberFormat()716 void TestQgsLabelingEngine::testNumberFormat()
717 {
718   QgsFeature f( vl->fields(), 1 );
719   f.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( -100, 30 ) ) );
720 
721   // make a fake render context
722   QSize size( 640, 480 );
723   QgsMapSettings mapSettings;
724   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
725   mapSettings.setOutputSize( size );
726   mapSettings.setExtent( vl->extent() );
727   mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
728   mapSettings.setOutputDpi( 96 );
729   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
730   QSet<QString> attributes;
731   QgsDefaultLabelingEngine engine;
732   engine.setMapSettings( mapSettings );
733 
734   // no change
735   QgsPalLayerSettings settings;
736   settings.fieldName = QStringLiteral( "110.112" );
737   settings.isExpression = true;
738 
739   QgsVectorLayerLabelProvider *provider = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test" ), true, &settings );
740   engine.addProvider( provider );
741   provider->prepare( context, attributes );
742   provider->registerFeature( f, context );
743   QCOMPARE( provider->mLabels.at( 0 )->labelText(), QStringLiteral( "110.112" ) );
744 
745   settings.fieldName = QStringLiteral( "-110.112" );
746   QgsVectorLayerLabelProvider *provider2 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test" ), true, &settings );
747   engine.addProvider( provider2 );
748   provider2->prepare( context, attributes );
749   provider2->registerFeature( f, context );
750   QCOMPARE( provider2->mLabels.at( 0 )->labelText(), QStringLiteral( "-110.112" ) );
751 
752   settings.fieldName = QStringLiteral( "110.112" );
753   settings.formatNumbers = true;
754   settings.decimals = 6;
755   QgsVectorLayerLabelProvider *provider3 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test" ), true, &settings );
756   engine.addProvider( provider3 );
757   provider3->prepare( context, attributes );
758   provider3->registerFeature( f, context );
759   QCOMPARE( provider3->mLabels.at( 0 )->labelText(), QStringLiteral( "110.112000" ) );
760 
761   settings.fieldName = QStringLiteral( "-110.112" );
762   QgsVectorLayerLabelProvider *provider4 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test" ), true, &settings );
763   engine.addProvider( provider4 );
764   provider4->prepare( context, attributes );
765   provider4->registerFeature( f, context );
766   QCOMPARE( provider4->mLabels.at( 0 )->labelText(), QStringLiteral( "-110.112000" ) );
767 
768   settings.fieldName = QStringLiteral( "110.112" );
769   settings.formatNumbers = true;
770   settings.plusSign = true;
771   QgsVectorLayerLabelProvider *provider5 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test" ), true, &settings );
772   engine.addProvider( provider5 );
773   provider5->prepare( context, attributes );
774   provider5->registerFeature( f, context );
775   QCOMPARE( provider5->mLabels.at( 0 )->labelText(), QStringLiteral( "+110.112000" ) );
776 
777   settings.fieldName = QStringLiteral( "-110.112" );
778   QgsVectorLayerLabelProvider *provider6 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test" ), true, &settings );
779   engine.addProvider( provider6 );
780   provider6->prepare( context, attributes );
781   provider6->registerFeature( f, context );
782   QCOMPARE( provider6->mLabels.at( 0 )->labelText(), QStringLiteral( "-110.112000" ) );
783 
784   settings.formatNumbers = false;
785   settings.fieldName = QStringLiteral( "110.112" );
786   QgsVectorLayerLabelProvider *provider7 = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test" ), true, &settings );
787   engine.addProvider( provider7 );
788   provider7->prepare( context, attributes );
789   provider7->registerFeature( f, context );
790   QCOMPARE( provider7->mLabels.at( 0 )->labelText(), QStringLiteral( "110.112" ) );
791 }
792 
testParticipatingLayers()793 void TestQgsLabelingEngine::testParticipatingLayers()
794 {
795   QgsDefaultLabelingEngine engine;
796   QVERIFY( engine.participatingLayers().isEmpty() );
797 
798   QgsPalLayerSettings settings1;
799   QgsVectorLayerLabelProvider *provider = new QgsVectorLayerLabelProvider( vl, QStringLiteral( "test" ), true, &settings1 );
800   engine.addProvider( provider );
801   QCOMPARE( engine.participatingLayers(), QList<QgsMapLayer *>() << vl );
802 
803   QgsVectorLayer *layer2 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer" ), QStringLiteral( "layer2" ), QStringLiteral( "memory" ) );
804   QgsPalLayerSettings settings2;
805   QgsVectorLayerLabelProvider *provider2 = new QgsVectorLayerLabelProvider( layer2, QStringLiteral( "test2" ), true, &settings2 );
806   engine.addProvider( provider2 );
807   QCOMPARE( qgis::listToSet( engine.participatingLayers() ), QSet< QgsMapLayer * >() << vl << layer2 );
808 
809   // add a rule-based labeling node
810   QgsRuleBasedLabeling::Rule *root = new QgsRuleBasedLabeling::Rule( nullptr );
811   QgsPalLayerSettings s1;
812   root->appendChild( new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings( s1 ) ) );
813   QgsPalLayerSettings s2;
814   root->appendChild( new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings( s2 ) ) );
815 
816   QgsVectorLayer *layer3 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer" ), QStringLiteral( "layer3" ), QStringLiteral( "memory" ) );
817   QgsRuleBasedLabelProvider *ruleProvider = new QgsRuleBasedLabelProvider( QgsRuleBasedLabeling( root ), layer3 );
818   engine.addProvider( ruleProvider );
819   QCOMPARE( qgis::listToSet( engine.participatingLayers() ), QSet< QgsMapLayer * >() << vl << layer2 << layer3 );
820 }
821 
imageCheck(const QString & testName,QImage & image,int mismatchCount)822 bool TestQgsLabelingEngine::imageCheck( const QString &testName, QImage &image, int mismatchCount )
823 {
824   //draw background
825   QImage imageWithBackground( image.width(), image.height(), QImage::Format_RGB32 );
826   QgsMultiRenderChecker::drawBackground( &imageWithBackground );
827   QPainter painter( &imageWithBackground );
828   painter.drawImage( 0, 0, image );
829   painter.end();
830 
831   mReport += "<h2>" + testName + "</h2>\n";
832   QString tempDir = QDir::tempPath() + '/';
833   QString fileName = tempDir + testName + ".png";
834   imageWithBackground.save( fileName, "PNG" );
835   QgsMultiRenderChecker checker;
836   checker.setControlPathPrefix( QStringLiteral( "labelingengine" ) );
837   checker.setControlName( "expected_" + testName );
838   checker.setRenderedImage( fileName );
839   checker.setColorTolerance( 2 );
840   bool resultFlag = checker.runTest( testName, mismatchCount );
841   mReport += checker.report();
842   return resultFlag;
843 }
844 
845 // See https://github.com/qgis/QGIS/issues/23431
testRegisterFeatureUnprojectible()846 void TestQgsLabelingEngine::testRegisterFeatureUnprojectible()
847 {
848   QgsPalLayerSettings settings;
849   settings.fieldName = QStringLiteral( "'aa label'" );
850   settings.isExpression = true;
851   settings.fitInPolygonOnly = true;
852 
853   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "polygon?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
854   QgsVectorLayerLabelProvider *provider = new QgsVectorLayerLabelProvider( vl2.get(), QStringLiteral( "test" ), true, &settings );
855   QgsFeature f( vl2->fields(), 1 );
856 
857   QString wkt1 = QStringLiteral( "POLYGON((0 0,8 0,8 -90,0 0))" );
858   f.setGeometry( QgsGeometry().fromWkt( wkt1 ) );
859 
860   // make a fake render context
861   QSize size( 640, 480 );
862   QgsMapSettings mapSettings;
863   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
864   QgsCoordinateReferenceSystem tgtCrs;
865   tgtCrs.createFromString( QStringLiteral( "EPSG:3857" ) );
866   mapSettings.setDestinationCrs( tgtCrs );
867 
868   mapSettings.setOutputSize( size );
869   mapSettings.setExtent( vl2->extent() );
870   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
871   mapSettings.setOutputDpi( 96 );
872   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
873   QSet<QString> attributes;
874   QgsDefaultLabelingEngine engine;
875   engine.setMapSettings( mapSettings );
876   engine.addProvider( provider );
877   provider->prepare( context, attributes );
878 
879   provider->registerFeature( f, context );
880   QCOMPARE( provider->mLabels.size(), 0 );
881 }
882 
testRotateHidePartial()883 void TestQgsLabelingEngine::testRotateHidePartial()
884 {
885   QgsPalLayerSettings settings;
886   setDefaultLabelParams( settings );
887 
888   QgsTextFormat format = settings.format();
889   format.setSize( 20 );
890   format.setColor( QColor( 0, 0, 0 ) );
891   settings.setFormat( format );
892 
893   settings.fieldName = QStringLiteral( "'label'" );
894   settings.isExpression = true;
895   settings.placement = QgsPalLayerSettings::OverPoint;
896 
897   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "polygon?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
898   vl2->setRenderer( new QgsNullSymbolRenderer() );
899 
900   QgsVectorLayerLabelProvider *provider = new QgsVectorLayerLabelProvider( vl2.get(), QStringLiteral( "test" ), true, &settings );
901   QgsFeature f( vl2->fields(), 1 );
902 
903   f.setGeometry( QgsGeometry().fromWkt( QStringLiteral( "POLYGON((0 0,8 0,8 8,0 8,0 0))" ) ) );
904   vl2->dataProvider()->addFeature( f );
905   f.setGeometry( QgsGeometry().fromWkt( QStringLiteral( "POLYGON((20 20,28 20,28 28,20 28,20 20))" ) ) );
906   vl2->dataProvider()->addFeature( f );
907   f.setGeometry( QgsGeometry().fromWkt( QStringLiteral( "POLYGON((0 20,8 20,8 28,0 28,0 20))" ) ) );
908   vl2->dataProvider()->addFeature( f );
909 
910   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
911   vl2->setLabelsEnabled( true );
912 
913   // make a fake render context
914   QSize size( 640, 480 );
915   QgsMapSettings mapSettings;
916   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
917   QgsCoordinateReferenceSystem tgtCrs;
918   tgtCrs.createFromString( QStringLiteral( "EPSG:4326" ) );
919   mapSettings.setDestinationCrs( tgtCrs );
920 
921   mapSettings.setOutputSize( size );
922   mapSettings.setExtent( vl2->extent() );
923   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
924   mapSettings.setOutputDpi( 96 );
925   mapSettings.setRotation( 45 );
926 
927   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
928   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
929   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
930   mapSettings.setLabelingEngineSettings( engineSettings );
931 
932   QgsMapRendererSequentialJob job( mapSettings );
933   job.start();
934   job.waitForFinished();
935 
936   QImage img = job.renderedImage();
937 
938   QPainter p( &img );
939   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
940   context.setPainter( &p );
941 
942   QgsDefaultLabelingEngine engine;
943   engine.setMapSettings( mapSettings );
944   engine.addProvider( provider );
945 
946   engine.run( context );
947   p.end();
948   engine.removeProvider( provider );
949 
950   QVERIFY( imageCheck( "label_rotate_hide_partial", img, 20 ) );
951 }
952 
testParallelLabelSmallFeature()953 void TestQgsLabelingEngine::testParallelLabelSmallFeature()
954 {
955   // Test rendering a small, closed linestring using parallel labeling
956   // This test assumes that NO label is drawn in this situation. In future we may want
957   // to revisit this and e.g. draw a centered horizontal label over the feature -- in which
958   // case the reference image here should be freely revised. But for now, we just don't
959   // want a hang/crash such as described in https://github.com/qgis/QGIS/issues/26174
960 
961   QgsPalLayerSettings settings;
962   setDefaultLabelParams( settings );
963 
964   QgsTextFormat format = settings.format();
965   format.setSize( 20 );
966   format.setColor( QColor( 0, 0, 0 ) );
967   settings.setFormat( format );
968 
969   settings.fieldName = QStringLiteral( "'long label which doesn\\'t fit'" );
970   settings.isExpression = true;
971   settings.placement = QgsPalLayerSettings::Line;
972 
973   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "linestring?crs=epsg:3148&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
974   vl2->setRenderer( new QgsNullSymbolRenderer() );
975 
976   QgsVectorLayerLabelProvider *provider = new QgsVectorLayerLabelProvider( vl2.get(), QStringLiteral( "test" ), true, &settings );
977   QgsFeature f( vl2->fields(), 1 );
978 
979   f.setGeometry( QgsGeometry().fromWkt( QStringLiteral( "MultiLineString ((491176.796876200591214 1277565.39028006233274937, 491172.03128372476203367 1277562.45040752924978733, 491167.67935446038609371 1277557.28786265244707465, 491165.36599104333436117 1277550.97473702346906066, 491165.35308923490811139 1277544.24074512091465294, 491166.8345245998352766 1277539.49665334494784474, 491169.47186020453227684 1277535.27191955596208572, 491173.11253597546601668 1277531.85408334922976792, 491179.02124191814800724 1277528.94421873707324266, 491185.57387020520400256 1277528.15719766705296934, 491192.01811734877992421 1277529.57064539520069957, 491197.62341773137450218 1277533.02997340611182153, 491201.74636711279163137 1277538.15941766835749149, 491203.92884904221864417 1277544.35095247370190918, 491203.9633954341406934 1277550.5652371181640774, 491202.02436481812037528 1277556.4815535971429199, 491198.296930403157603 1277561.48062952468171716, 491193.17346247035311535 1277565.0647635399363935, 491187.82046439842088148 1277566.747082503978163, 491182.21622701874002814 1277566.85931688314303756, 491176.796876200591214 1277565.39028006233274937))" ) ) );
980   vl2->dataProvider()->addFeature( f );
981 
982   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
983   vl2->setLabelsEnabled( true );
984 
985   // make a fake render context
986   QSize size( 640, 480 );
987   QgsMapSettings mapSettings;
988   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
989   QgsCoordinateReferenceSystem tgtCrs;
990   tgtCrs.createFromString( QStringLiteral( "EPSG:3148" ) );
991   mapSettings.setDestinationCrs( tgtCrs );
992 
993   mapSettings.setOutputSize( size );
994   mapSettings.setExtent( QgsRectangle( 490359.7, 1276862.1, 492587.8, 1278500.0 ) );
995   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
996   mapSettings.setOutputDpi( 96 );
997 
998   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
999   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1000   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1001   mapSettings.setLabelingEngineSettings( engineSettings );
1002 
1003   QgsMapRendererSequentialJob job( mapSettings );
1004   job.start();
1005   job.waitForFinished();
1006 
1007   QImage img = job.renderedImage();
1008 
1009   QPainter p( &img );
1010   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
1011   context.setPainter( &p );
1012 
1013   QgsDefaultLabelingEngine engine;
1014   engine.setMapSettings( mapSettings );
1015   engine.addProvider( provider );
1016 
1017   engine.run( context );
1018   p.end();
1019   engine.removeProvider( provider );
1020 
1021   // no need to actually check the result here -- we were just testing that no hang/crash occurred
1022   //  QVERIFY( imageCheck( "label_rotate_hide_partial", img, 20 ) );
1023 }
1024 
testAdjacentParts()1025 void TestQgsLabelingEngine::testAdjacentParts()
1026 {
1027   // test polygon layer with multipart feature with adjacent parts
1028   QgsPalLayerSettings settings;
1029   setDefaultLabelParams( settings );
1030 
1031   QgsTextFormat format = settings.format();
1032   format.setSize( 20 );
1033   format.setColor( QColor( 0, 0, 0 ) );
1034   settings.setFormat( format );
1035 
1036   settings.fieldName = QStringLiteral( "'X'" );
1037   settings.isExpression = true;
1038   settings.placement = QgsPalLayerSettings::OverPoint;
1039   settings.labelPerPart = true;
1040 
1041   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "Polygon?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1042   vl2->setRenderer( new QgsNullSymbolRenderer() );
1043 
1044   QgsFeature f;
1045   f.setAttributes( QgsAttributes() << 1 );
1046   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "MultiPolygon (((1967901.6872910603415221 5162590.11975561361759901, 1967905.31832842249423265 5162591.80023225769400597, 1967907.63076798897236586 5162586.43503414187580347, 1967903.84105980419553816 5162584.57283254805952311, 1967901.6872910603415221 5162590.11975561361759901)),((1967901.64785283687524498 5162598.3270823871716857, 1967904.82891705213114619 5162601.06552503909915686, 1967910.82140435534529388 5162587.99774718284606934, 1967907.63076798897236586 5162586.43503414187580347, 1967905.31832842249423265 5162591.80023225769400597, 1967901.6872910603415221 5162590.11975561361759901, 1967899.27472299290820956 5162596.28855143301188946, 1967901.64785283687524498 5162598.3270823871716857)),((1967904.82891705213114619 5162601.06552503909915686, 1967901.64785283687524498 5162598.3270823871716857, 1967884.28552994946949184 5162626.09785370342433453, 1967895.81538487318903208 5162633.84423183929175138, 1967901.64141261484473944 5162624.63927845563739538, 1967906.47453573765233159 5162616.87410452589392662, 1967913.7844126324634999 5162604.47178338281810284, 1967909.58057221467606723 5162602.89022256527096033, 1967904.82891705213114619 5162601.06552503909915686)))" ) ) );
1047   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1048 
1049   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1050   vl2->setLabelsEnabled( true );
1051 
1052   // make a fake render context
1053   QSize size( 640, 480 );
1054   QgsMapSettings mapSettings;
1055   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1056   mapSettings.setDestinationCrs( vl2->crs() );
1057 
1058   mapSettings.setOutputSize( size );
1059   mapSettings.setExtent( f.geometry().boundingBox() );
1060   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1061   mapSettings.setOutputDpi( 96 );
1062 
1063   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1064   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1065   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1066   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
1067   mapSettings.setLabelingEngineSettings( engineSettings );
1068 
1069   QgsMapRendererSequentialJob job( mapSettings );
1070   job.start();
1071   job.waitForFinished();
1072 
1073   QImage img = job.renderedImage();
1074   QVERIFY( imageCheck( QStringLiteral( "label_adjacent_parts" ), img, 20 ) );
1075 }
1076 
testTouchingParts()1077 void TestQgsLabelingEngine::testTouchingParts()
1078 {
1079   // test line layer with multipart feature with touching (but unmerged) parts
1080   QgsPalLayerSettings settings;
1081   setDefaultLabelParams( settings );
1082 
1083   QgsTextFormat format = settings.format();
1084   format.setSize( 20 );
1085   format.setColor( QColor( 0, 0, 0 ) );
1086   settings.setFormat( format );
1087 
1088   settings.fieldName = QStringLiteral( "'XXXXXXXXXXXXXXXXXXXXXXXXXX'" );
1089   settings.isExpression = true;
1090   settings.placement = QgsPalLayerSettings::Curved;
1091   settings.labelPerPart = false;
1092   settings.lineSettings().setMergeLines( true );
1093 
1094   // if treated individually, none of these parts are long enough for the label to fit -- but the label should be rendered if the mergeLines setting is true,
1095   // because the parts should be merged into a single linestring
1096   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "MultiLineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1097   vl2->setRenderer( new QgsNullSymbolRenderer() );
1098 
1099   QgsFeature f;
1100   f.setAttributes( QgsAttributes() << 1 );
1101   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "MultiLineString ((190000 5000010, 190050 5000000), (190050 5000000, 190100 5000000), (190200 5000000, 190150 5000000), (190150 5000000, 190100 5000000))" ) ) );
1102   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1103 
1104   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1105   vl2->setLabelsEnabled( true );
1106 
1107   // make a fake render context
1108   QSize size( 640, 480 );
1109   QgsMapSettings mapSettings;
1110   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1111   mapSettings.setDestinationCrs( vl2->crs() );
1112 
1113   mapSettings.setOutputSize( size );
1114   mapSettings.setExtent( f.geometry().boundingBox() );
1115   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1116   mapSettings.setOutputDpi( 96 );
1117 
1118   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1119   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1120   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1121   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
1122   mapSettings.setLabelingEngineSettings( engineSettings );
1123 
1124   QgsMapRendererSequentialJob job( mapSettings );
1125   job.start();
1126   job.waitForFinished();
1127 
1128   QImage img = job.renderedImage();
1129   QVERIFY( imageCheck( QStringLiteral( "label_multipart_touching_lines" ), img, 20 ) );
1130 }
1131 
testMergingLinesWithForks()1132 void TestQgsLabelingEngine::testMergingLinesWithForks()
1133 {
1134   // test that the "merge connected features" setting works well with line networks
1135   // containing forks and small side branches
1136   QgsPalLayerSettings settings;
1137   setDefaultLabelParams( settings );
1138 
1139   QgsTextFormat format = settings.format();
1140   format.setSize( 20 );
1141   format.setColor( QColor( 0, 0, 0 ) );
1142   settings.setFormat( format );
1143 
1144   settings.fieldName = QStringLiteral( "'XXXXXXXXXXXXXXXXXXXXXXXXXX'" );
1145   settings.isExpression = true;
1146   settings.placement = QgsPalLayerSettings::Curved;
1147   settings.labelPerPart = false;
1148   settings.lineSettings().setMergeLines( true );
1149 
1150   // if treated individually, none of these parts are long enough for the label to fit -- but the label should be rendered if the mergeLines setting is true
1151   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1152   vl2->setRenderer( new QgsNullSymbolRenderer() );
1153 
1154   QgsFeature f;
1155   f.setAttributes( QgsAttributes() << 1 );
1156   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190000 5000010, 190100 5000000)" ) ) );
1157   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1158   // side branch
1159   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190100 5000000, 190100 5000010)" ) ) );
1160   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1161   // side branch
1162   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190100 5000000, 190100 4999995)" ) ) );
1163   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1164   // main road continues, note that we deliberately split this up into non-consecutive sections, just for extra checks!
1165   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190120 5000000, 190200 5000000)" ) ) );
1166   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1167   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190120 5000000, 190100 5000000)" ) ) );
1168   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1169 
1170   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1171   vl2->setLabelsEnabled( true );
1172 
1173   // make a fake render context
1174   QSize size( 640, 480 );
1175   QgsMapSettings mapSettings;
1176   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1177   mapSettings.setDestinationCrs( vl2->crs() );
1178 
1179   mapSettings.setOutputSize( size );
1180   mapSettings.setExtent( vl2->extent() );
1181   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1182   mapSettings.setOutputDpi( 96 );
1183 
1184   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1185   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1186   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1187   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
1188   mapSettings.setLabelingEngineSettings( engineSettings );
1189 
1190   QgsMapRendererSequentialJob job( mapSettings );
1191   job.start();
1192   job.waitForFinished();
1193 
1194   QImage img = job.renderedImage();
1195   QVERIFY( imageCheck( QStringLiteral( "label_multipart_touching_branches" ), img, 20 ) );
1196 }
1197 
testCurvedLabelsWithTinySegments()1198 void TestQgsLabelingEngine::testCurvedLabelsWithTinySegments()
1199 {
1200   // test drawing curved labels when input linestring has many small segments
1201   QgsPalLayerSettings settings;
1202   setDefaultLabelParams( settings );
1203 
1204   QgsTextFormat format = settings.format();
1205   format.setSize( 20 );
1206   format.setColor( QColor( 0, 0, 0 ) );
1207   settings.setFormat( format );
1208 
1209   settings.fieldName = QStringLiteral( "'XXXXXXXXXXXXXXXXXXXXXXXXXX'" );
1210   settings.isExpression = true;
1211   settings.placement = QgsPalLayerSettings::Curved;
1212 
1213   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1214   vl2->setRenderer( new QgsNullSymbolRenderer() );
1215 
1216   QgsFeature f;
1217   f.setAttributes( QgsAttributes() << 1 );
1218   // our geometry starts with many small segments, followed by long ones
1219   QgsGeometry g( QgsGeometry::fromWkt( QStringLiteral( "LineString (190000 5000010, 190100 5000000)" ) ) );
1220   g = g.densifyByCount( 100 );
1221   qgsgeometry_cast< QgsLineString * >( g.get() )->addVertex( QgsPoint( 190200, 5000000 ) );
1222   f.setGeometry( g );
1223   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1224 
1225   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1226   vl2->setLabelsEnabled( true );
1227 
1228   // make a fake render context
1229   QSize size( 640, 480 );
1230   QgsMapSettings mapSettings;
1231   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1232   mapSettings.setDestinationCrs( vl2->crs() );
1233 
1234   mapSettings.setOutputSize( size );
1235   mapSettings.setExtent( g.boundingBox() );
1236   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1237   mapSettings.setOutputDpi( 96 );
1238   mapSettings.setFlag( QgsMapSettings::UseRenderingOptimization, false );
1239 
1240   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1241   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1242   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1243   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
1244   mapSettings.setLabelingEngineSettings( engineSettings );
1245 
1246   QgsMapRendererSequentialJob job( mapSettings );
1247   job.start();
1248   job.waitForFinished();
1249 
1250   QImage img = job.renderedImage();
1251   QVERIFY( imageCheck( QStringLiteral( "label_curved_label_small_segments" ), img, 20 ) );
1252 }
1253 
testCurvedLabelCorrectLinePlacement()1254 void TestQgsLabelingEngine::testCurvedLabelCorrectLinePlacement()
1255 {
1256   // test drawing curved labels when input linestring has many small segments
1257   QgsPalLayerSettings settings;
1258   setDefaultLabelParams( settings );
1259 
1260   QgsTextFormat format = settings.format();
1261   format.setSize( 20 );
1262   format.setColor( QColor( 0, 0, 0 ) );
1263   settings.setFormat( format );
1264 
1265   settings.fieldName = QStringLiteral( "'XXXXXXXXXXXXXXXXXXXXXXXXXX'" );
1266   settings.isExpression = true;
1267   settings.placement = QgsPalLayerSettings::Curved;
1268   settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine | QgsLabeling::LinePlacementFlag::MapOrientation );
1269   settings.maxCurvedCharAngleIn = 99;
1270   settings.maxCurvedCharAngleOut = 99;
1271 
1272   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1273   vl2->setRenderer( new QgsNullSymbolRenderer() );
1274 
1275   QgsFeature f;
1276   f.setAttributes( QgsAttributes() << 1 );
1277   // Geometry which roughly curves around from "1 o'clock" anticlockwise to 6 o'clock.
1278   QgsGeometry g( QgsGeometry::fromWkt( QStringLiteral( "LineString (0.30541596873255172 0.3835845896147404, -0.08989391401451696 0.21831379117811278, -0.33668341708542704 -0.01619207146845336, -0.156895589056393 -0.20714684533780003, 0.02735901730876611 -0.21496370742601911)" ) ) );
1279   f.setGeometry( g );
1280   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1281 
1282   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1283   vl2->setLabelsEnabled( true );
1284 
1285   // make a fake render context
1286   QSize size( 640, 480 );
1287   QgsMapSettings mapSettings;
1288   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1289   mapSettings.setDestinationCrs( vl2->crs() );
1290 
1291   mapSettings.setOutputSize( size );
1292   mapSettings.setExtent( g.boundingBox() );
1293   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1294   mapSettings.setOutputDpi( 96 );
1295 
1296   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1297   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1298   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1299   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
1300   mapSettings.setLabelingEngineSettings( engineSettings );
1301 
1302   QgsMapRendererSequentialJob job( mapSettings );
1303   job.start();
1304   job.waitForFinished();
1305 
1306   QImage img = job.renderedImage();
1307   QVERIFY( imageCheck( QStringLiteral( "label_curved_label_above_1" ), img, 20 ) );
1308 
1309   // and below...
1310   settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::BelowLine | QgsLabeling::LinePlacementFlag::MapOrientation );
1311   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1312 
1313   QgsMapRendererSequentialJob job2( mapSettings );
1314   job2.start();
1315   job2.waitForFinished();
1316 
1317   img = job2.renderedImage();
1318   QVERIFY( imageCheck( QStringLiteral( "label_curved_label_below_1" ), img, 20 ) );
1319 }
1320 
testCurvedLabelNegativeDistance()1321 void TestQgsLabelingEngine::testCurvedLabelNegativeDistance()
1322 {
1323   // test line label rendering with negative distance
1324   QgsPalLayerSettings settings;
1325   setDefaultLabelParams( settings );
1326 
1327   QgsTextFormat format = settings.format();
1328   format.setSize( 20 );
1329   format.setColor( QColor( 0, 0, 0 ) );
1330   settings.setFormat( format );
1331 
1332   settings.fieldName = QStringLiteral( "'XXXXXXXXXXXXXXXXXXXXXXXXXX'" );
1333   settings.isExpression = true;
1334   settings.placement = QgsPalLayerSettings::Curved;
1335   settings.labelPerPart = false;
1336   settings.dist = -5;
1337 
1338   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1339   vl2->setRenderer( new QgsNullSymbolRenderer() );
1340 
1341   QgsFeature f;
1342   f.setAttributes( QgsAttributes() << 1 );
1343   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190000 5000010, 190100 5000000, 190200 5000000)" ) ) );
1344   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1345 
1346   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1347   vl2->setLabelsEnabled( true );
1348 
1349   // make a fake render context
1350   QSize size( 640, 480 );
1351   QgsMapSettings mapSettings;
1352   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1353   mapSettings.setDestinationCrs( vl2->crs() );
1354 
1355   mapSettings.setOutputSize( size );
1356   mapSettings.setExtent( f.geometry().boundingBox() );
1357   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1358   mapSettings.setOutputDpi( 96 );
1359 
1360   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1361   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1362   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1363   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
1364   mapSettings.setLabelingEngineSettings( engineSettings );
1365 
1366   QgsMapRendererSequentialJob job( mapSettings );
1367   job.start();
1368   job.waitForFinished();
1369 
1370   QImage img = job.renderedImage();
1371   QVERIFY( imageCheck( QStringLiteral( "label_curved_negative_distance" ), img, 20 ) );
1372 }
1373 
testCurvedLabelOnSmallLineNearCenter()1374 void TestQgsLabelingEngine::testCurvedLabelOnSmallLineNearCenter()
1375 {
1376   // test a small line relative to label size still gives sufficient candidates to ensure more centered placements
1377   // are found
1378   QgsPalLayerSettings settings;
1379   setDefaultLabelParams( settings );
1380 
1381   QgsTextFormat format = settings.format();
1382   format.setSize( 20 );
1383   format.setColor( QColor( 0, 0, 0 ) );
1384   settings.setFormat( format );
1385 
1386   settings.fieldName = QStringLiteral( "'XXXXX'" );
1387   settings.isExpression = true;
1388   settings.placement = QgsPalLayerSettings::Curved;
1389   settings.labelPerPart = false;
1390 
1391   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1392   vl2->setRenderer( new QgsNullSymbolRenderer() );
1393 
1394   QgsFeature f;
1395   f.setAttributes( QgsAttributes() << 1 );
1396   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190080 5000010, 190100 5000000, 190120 5000000)" ) ) );
1397   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1398 
1399   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1400   vl2->setLabelsEnabled( true );
1401 
1402   // make a fake render context
1403   QSize size( 640, 480 );
1404   QgsMapSettings mapSettings;
1405   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1406   mapSettings.setDestinationCrs( vl2->crs() );
1407 
1408   mapSettings.setOutputSize( size );
1409   mapSettings.setExtent( QgsRectangle( 190000, 5000000, 190200, 5000010 ) );
1410   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1411   mapSettings.setOutputDpi( 96 );
1412 
1413   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1414   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1415   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1416   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
1417   mapSettings.setLabelingEngineSettings( engineSettings );
1418 
1419   QgsMapRendererSequentialJob job( mapSettings );
1420   job.start();
1421   job.waitForFinished();
1422 
1423   QImage img = job.renderedImage();
1424   QVERIFY( imageCheck( QStringLiteral( "label_curved_small_feature_centered" ), img, 20 ) );
1425 }
1426 
testRepeatDistanceWithSmallLine()1427 void TestQgsLabelingEngine::testRepeatDistanceWithSmallLine()
1428 {
1429   // test a small line relative to label size still gives sufficient candidates to ensure more centered placements
1430   // are found
1431   QgsPalLayerSettings settings;
1432   setDefaultLabelParams( settings );
1433 
1434   QgsTextFormat format = settings.format();
1435   format.setSize( 20 );
1436   format.setColor( QColor( 0, 0, 0 ) );
1437   settings.setFormat( format );
1438 
1439   settings.fieldName = QStringLiteral( "'XXXXXXX'" );
1440   settings.isExpression = true;
1441   settings.placement = QgsPalLayerSettings::Curved;
1442   settings.labelPerPart = false;
1443   settings.repeatDistance = 55;
1444 
1445   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1446   vl2->setRenderer( new QgsNullSymbolRenderer() );
1447 
1448   QgsFeature f;
1449   f.setAttributes( QgsAttributes() << 1 );
1450   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190050 5000000, 190150 5000000)" ) ) );
1451   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1452 
1453   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1454   vl2->setLabelsEnabled( true );
1455 
1456   // make a fake render context
1457   QSize size( 640, 480 );
1458   QgsMapSettings mapSettings;
1459   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1460   mapSettings.setDestinationCrs( vl2->crs() );
1461 
1462   mapSettings.setOutputSize( size );
1463   mapSettings.setExtent( QgsRectangle( 190000, 5000000, 190200, 5000010 ) );
1464   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1465   mapSettings.setOutputDpi( 96 );
1466 
1467   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1468   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1469   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1470   engineSettings.setFlag( QgsLabelingEngineSettings::DrawUnplacedLabels, true );
1471   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
1472   mapSettings.setLabelingEngineSettings( engineSettings );
1473 
1474   QgsMapRendererSequentialJob job( mapSettings );
1475   job.start();
1476   job.waitForFinished();
1477 
1478   QImage img = job.renderedImage();
1479   QVERIFY( imageCheck( QStringLiteral( "label_repeat_distance_with_small_line" ), img, 20 ) );
1480 }
1481 
testParallelPlacementPreferAbove()1482 void TestQgsLabelingEngine::testParallelPlacementPreferAbove()
1483 {
1484   // given the choice of above or below placement, labels should always be placed above
1485   QgsPalLayerSettings settings;
1486   setDefaultLabelParams( settings );
1487 
1488   QgsTextFormat format = settings.format();
1489   format.setSize( 20 );
1490   format.setColor( QColor( 0, 0, 0 ) );
1491   settings.setFormat( format );
1492 
1493   settings.fieldName = QStringLiteral( "'XXXXXXXX'" );
1494   settings.isExpression = true;
1495   settings.placement = QgsPalLayerSettings::Line;
1496   settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine | QgsLabeling::LinePlacementFlag::BelowLine | QgsLabeling::LinePlacementFlag::MapOrientation );
1497   settings.labelPerPart = false;
1498 
1499   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1500   vl2->setRenderer( new QgsNullSymbolRenderer() );
1501 
1502   QgsFeature f;
1503   f.setAttributes( QgsAttributes() << 1 );
1504   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190000 5000010, 190200 5000000)" ) ) );
1505   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1506 
1507   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1508   vl2->setLabelsEnabled( true );
1509 
1510   // make a fake render context
1511   QSize size( 640, 480 );
1512   QgsMapSettings mapSettings;
1513   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1514   mapSettings.setDestinationCrs( vl2->crs() );
1515 
1516   mapSettings.setOutputSize( size );
1517   mapSettings.setExtent( f.geometry().boundingBox() );
1518   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1519   mapSettings.setOutputDpi( 96 );
1520 
1521   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1522   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1523   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1524   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
1525   mapSettings.setLabelingEngineSettings( engineSettings );
1526 
1527   QgsMapRendererSequentialJob job( mapSettings );
1528   job.start();
1529   job.waitForFinished();
1530 
1531   QImage img = job.renderedImage();
1532   QVERIFY( imageCheck( QStringLiteral( "parallel_prefer_above" ), img, 20 ) );
1533 }
1534 
testLabelBoundary()1535 void TestQgsLabelingEngine::testLabelBoundary()
1536 {
1537   // test that no labels are drawn outside of the specified label boundary
1538   QgsPalLayerSettings settings;
1539   setDefaultLabelParams( settings );
1540 
1541   QgsTextFormat format = settings.format();
1542   format.setSize( 20 );
1543   format.setColor( QColor( 0, 0, 0 ) );
1544   settings.setFormat( format );
1545 
1546   settings.fieldName = QStringLiteral( "'X'" );
1547   settings.isExpression = true;
1548   settings.placement = QgsPalLayerSettings::OverPoint;
1549 
1550   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1551   vl2->setRenderer( new QgsNullSymbolRenderer() );
1552 
1553   QgsFeature f( vl2->fields(), 1 );
1554 
1555   for ( int x = 0; x < 15; x++ )
1556   {
1557     for ( int y = 0; y < 12; y++ )
1558     {
1559       f.setGeometry( qgis::make_unique< QgsPoint >( x, y ) );
1560       vl2->dataProvider()->addFeature( f );
1561     }
1562   }
1563 
1564   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1565   vl2->setLabelsEnabled( true );
1566 
1567   // make a fake render context
1568   QSize size( 640, 480 );
1569   QgsMapSettings mapSettings;
1570   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1571   QgsCoordinateReferenceSystem tgtCrs;
1572   tgtCrs.createFromString( QStringLiteral( "EPSG:4326" ) );
1573   mapSettings.setDestinationCrs( tgtCrs );
1574 
1575   mapSettings.setOutputSize( size );
1576   mapSettings.setExtent( vl2->extent() );
1577   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1578   mapSettings.setOutputDpi( 96 );
1579 
1580   mapSettings.setLabelBoundaryGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((3 1, 12 1, 12 9, 3 9, 3 1),(8 4, 10 4, 10 7, 8 7, 8 4))" ) ) );
1581 
1582   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1583   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1584   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1585   mapSettings.setLabelingEngineSettings( engineSettings );
1586 
1587   QgsMapRendererSequentialJob job( mapSettings );
1588   job.start();
1589   job.waitForFinished();
1590 
1591   QImage img = job.renderedImage();
1592   QVERIFY( imageCheck( QStringLiteral( "label_boundary_geometry" ), img, 20 ) );
1593 
1594   // with rotation
1595   mapSettings.setRotation( 45 );
1596   QgsMapRendererSequentialJob job2( mapSettings );
1597   job2.start();
1598   job2.waitForFinished();
1599 
1600   img = job2.renderedImage();
1601   QVERIFY( imageCheck( QStringLiteral( "rotated_label_boundary_geometry" ), img, 20 ) );
1602 }
1603 
testLabelBlockingRegion()1604 void TestQgsLabelingEngine::testLabelBlockingRegion()
1605 {
1606   // test that no labels are drawn inside blocking regions
1607   QgsPalLayerSettings settings;
1608   setDefaultLabelParams( settings );
1609 
1610   QgsTextFormat format = settings.format();
1611   format.setSize( 20 );
1612   format.setColor( QColor( 0, 0, 0 ) );
1613   settings.setFormat( format );
1614 
1615   settings.fieldName = QStringLiteral( "'X'" );
1616   settings.isExpression = true;
1617   settings.placement = QgsPalLayerSettings::OverPoint;
1618 
1619   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1620   vl2->setRenderer( new QgsNullSymbolRenderer() );
1621 
1622   QgsFeature f( vl2->fields(), 1 );
1623 
1624   for ( int x = 0; x < 15; x++ )
1625   {
1626     for ( int y = 0; y < 12; y++ )
1627     {
1628       f.setGeometry( qgis::make_unique< QgsPoint >( x, y ) );
1629       vl2->dataProvider()->addFeature( f );
1630     }
1631   }
1632 
1633   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1634   vl2->setLabelsEnabled( true );
1635 
1636   // make a fake render context
1637   QSize size( 640, 480 );
1638   QgsMapSettings mapSettings;
1639   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1640   QgsCoordinateReferenceSystem tgtCrs;
1641   tgtCrs.createFromString( QStringLiteral( "EPSG:4326" ) );
1642   mapSettings.setDestinationCrs( tgtCrs );
1643 
1644   mapSettings.setOutputSize( size );
1645   mapSettings.setExtent( vl2->extent() );
1646   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1647   mapSettings.setOutputDpi( 96 );
1648 
1649   QList< QgsLabelBlockingRegion > regions;
1650   regions << QgsLabelBlockingRegion( QgsGeometry::fromWkt( QStringLiteral( "Polygon((6 1, 12 1, 12 9, 6 9, 6 1),(8 4, 10 4, 10 7, 8 7, 8 4))" ) ) );
1651   regions << QgsLabelBlockingRegion( QgsGeometry::fromWkt( QStringLiteral( "Polygon((0 0, 3 0, 3 3, 0 3, 0 0))" ) ) );
1652   mapSettings.setLabelBlockingRegions( regions );
1653 
1654   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1655   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1656   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1657   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
1658   mapSettings.setLabelingEngineSettings( engineSettings );
1659 
1660   QgsMapRendererSequentialJob job( mapSettings );
1661   job.start();
1662   job.waitForFinished();
1663 
1664   QImage img = job.renderedImage();
1665   QVERIFY( imageCheck( QStringLiteral( "label_blocking_geometry" ), img, 20 ) );
1666 
1667   // with rotation
1668   mapSettings.setRotation( 45 );
1669   QgsMapRendererSequentialJob job2( mapSettings );
1670   job2.start();
1671   job2.waitForFinished();
1672 
1673   img = job2.renderedImage();
1674   QVERIFY( imageCheck( QStringLiteral( "rotated_label_blocking_geometry" ), img, 20 ) );
1675 
1676   // blocking regions WITH label margin
1677   mapSettings.setRotation( 0 );
1678   mapSettings.setLabelBoundaryGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((1 1, 14 1, 14 9, 1 9, 1 1))" ) ) );
1679 
1680   QgsMapRendererSequentialJob job3( mapSettings );
1681   job3.start();
1682   job3.waitForFinished();
1683 
1684   img = job3.renderedImage();
1685   QVERIFY( imageCheck( QStringLiteral( "label_blocking_boundary_geometry" ), img, 20 ) );
1686 }
1687 
testLabelRotationWithReprojection()1688 void TestQgsLabelingEngine::testLabelRotationWithReprojection()
1689 {
1690   // test combination of map rotation with reprojected layer
1691   QgsPalLayerSettings settings;
1692   setDefaultLabelParams( settings );
1693 
1694   QgsTextFormat format = settings.format();
1695   format.setSize( 20 );
1696   format.setColor( QColor( 0, 0, 0 ) );
1697   settings.setFormat( format );
1698 
1699   settings.fieldName = QStringLiteral( "'X'" );
1700   settings.isExpression = true;
1701   settings.placement = QgsPalLayerSettings::OverPoint;
1702 
1703   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1704   vl2->setRenderer( new QgsNullSymbolRenderer() );
1705 
1706   QgsFeature f;
1707   f.setAttributes( QgsAttributes() << 1 );
1708   f.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( -6.250851540391068, 53.335006994584944 ) ) );
1709   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1710   f.setAttributes( QgsAttributes() << 2 );
1711   f.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( -21.950014487179544, 64.150023619739216 ) ) );
1712   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1713   f.setAttributes( QgsAttributes() << 3 );
1714   f.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( -0.118667702475932, 51.5019405883275 ) ) );
1715   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1716 
1717   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1718   vl2->setLabelsEnabled( true );
1719 
1720   // make a fake render context
1721   QSize size( 640, 480 );
1722   QgsMapSettings mapSettings;
1723   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1724   QgsCoordinateReferenceSystem tgtCrs( QStringLiteral( "EPSG:3857" ) );
1725   mapSettings.setDestinationCrs( tgtCrs );
1726 
1727   mapSettings.setOutputSize( size );
1728   mapSettings.setExtent( QgsRectangle( -4348530.5, 5618594.3, 2516176.1, 12412237.9 ) );
1729   mapSettings.setRotation( 60 );
1730   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1731   mapSettings.setOutputDpi( 96 );
1732 
1733   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1734   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1735   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1736   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
1737   mapSettings.setLabelingEngineSettings( engineSettings );
1738 
1739   QgsMapRendererSequentialJob job( mapSettings );
1740   job.start();
1741   job.waitForFinished();
1742 
1743   QImage img = job.renderedImage();
1744   QVERIFY( imageCheck( QStringLiteral( "label_rotate_with_reproject" ), img, 20 ) );
1745 }
1746 
drawUnplaced()1747 void TestQgsLabelingEngine::drawUnplaced()
1748 {
1749   // test drawing unplaced labels
1750   QgsPalLayerSettings settings;
1751   setDefaultLabelParams( settings );
1752 
1753   // first create two overlapping point labels
1754   QgsTextFormat format = settings.format();
1755   format.setSize( 50 );
1756   format.setColor( QColor( 0, 0, 0 ) );
1757   settings.setFormat( format );
1758 
1759   settings.fieldName = QStringLiteral( "'XX'" );
1760   settings.isExpression = true;
1761   settings.placement = QgsPalLayerSettings::OverPoint;
1762   settings.priority = 3;
1763   settings.obstacleSettings().setFactor( 0 );
1764 
1765   std::unique_ptr< QgsVectorLayer> vl1( new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1766   vl1->setRenderer( new QgsNullSymbolRenderer() );
1767 
1768   QgsFeature f;
1769   f.setAttributes( QgsAttributes() << 1 );
1770   f.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( -6.250851540391068, 53.335006994584944 ) ) );
1771   QVERIFY( vl1->dataProvider()->addFeature( f ) );
1772 
1773   vl1->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1774   vl1->setLabelsEnabled( true );
1775 
1776   // second layer
1777   settings.fieldName = QStringLiteral( "'YY'" );
1778   settings.isExpression = true;
1779   settings.placement = QgsPalLayerSettings::OverPoint;
1780   settings.priority = 5; // higher priority - YY should be placed, not XX
1781   settings.obstacleSettings().setFactor( 0 );
1782   format.setSize( 90 );
1783   settings.setFormat( format );
1784 
1785   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1786   vl2->setRenderer( new QgsNullSymbolRenderer() );
1787   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1788 
1789   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1790   vl2->setLabelsEnabled( true );
1791 
1792   // test a label with 0 candidates (line is too short for label)
1793   std::unique_ptr< QgsVectorLayer> vl3( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1794   vl3->setRenderer( new QgsNullSymbolRenderer() );
1795   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString(-6.250851540391068 60.6, -6.250851640391068 60.6 )" ) ) );
1796   QVERIFY( vl3->dataProvider()->addFeature( f ) );
1797 
1798   settings.placement = QgsPalLayerSettings::Curved;
1799   vl3->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1800   vl3->setLabelsEnabled( true );
1801 
1802   // make a fake render context
1803   QSize size( 640, 480 );
1804   QgsMapSettings mapSettings;
1805   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1806   QgsCoordinateReferenceSystem tgtCrs( QStringLiteral( "EPSG:3857" ) );
1807   mapSettings.setDestinationCrs( tgtCrs );
1808 
1809   mapSettings.setOutputSize( size );
1810   mapSettings.setExtent( QgsRectangle( -4348530.5, 5618594.3, 2516176.1, 12412237.9 ) );
1811   mapSettings.setRotation( 60 );
1812   mapSettings.setLayers( QList<QgsMapLayer *>() << vl1.get() << vl2.get() << vl3.get() );
1813   mapSettings.setOutputDpi( 96 );
1814 
1815   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1816   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1817   engineSettings.setFlag( QgsLabelingEngineSettings::DrawUnplacedLabels, true );
1818   engineSettings.setUnplacedLabelColor( QColor( 255, 0, 255 ) );
1819   mapSettings.setLabelingEngineSettings( engineSettings );
1820 
1821   QgsMapRendererSequentialJob job( mapSettings );
1822   job.start();
1823   job.waitForFinished();
1824 
1825   QImage img = job.renderedImage();
1826   QVERIFY( imageCheck( QStringLiteral( "unplaced_labels" ), img, 20 ) );
1827 }
1828 
labelingResults()1829 void TestQgsLabelingEngine::labelingResults()
1830 {
1831   // test retrieval of labeling results
1832   QgsPalLayerSettings settings;
1833   setDefaultLabelParams( settings );
1834 
1835   QgsTextFormat format = settings.format();
1836   format.setSize( 20 );
1837   format.setColor( QColor( 0, 0, 0 ) );
1838   settings.setFormat( format );
1839 
1840   settings.fieldName = QStringLiteral( "\"id\"" );
1841   settings.isExpression = true;
1842   settings.placement = QgsPalLayerSettings::OverPoint;
1843 
1844 
1845   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
1846   vl2->setRenderer( new QgsNullSymbolRenderer() );
1847 
1848   QgsFeature f;
1849   f.setAttributes( QgsAttributes() << 1 );
1850   f.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( -6.250851540391068, 53.335006994584944 ) ) );
1851   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1852   f.setAttributes( QgsAttributes() << 8888 );
1853   f.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( -21.950014487179544, 64.150023619739216 ) ) );
1854   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1855   f.setAttributes( QgsAttributes() << 33333 );
1856   f.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( -0.118667702475932, 51.5019405883275 ) ) );
1857   QVERIFY( vl2->dataProvider()->addFeature( f ) );
1858   vl2->updateExtents();
1859 
1860   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
1861   vl2->setLabelsEnabled( true );
1862 
1863   // make a fake render context
1864   QSize size( 640, 480 );
1865   QgsMapSettings mapSettings;
1866   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
1867   QgsCoordinateReferenceSystem tgtCrs( QStringLiteral( "EPSG:3857" ) );
1868   mapSettings.setDestinationCrs( tgtCrs );
1869 
1870   mapSettings.setOutputSize( size );
1871   mapSettings.setExtent( QgsRectangle( -4137976.6, 6557092.6, 1585557.4, 9656515.0 ) );
1872 // mapSettings.setRotation( 60 );
1873   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
1874   mapSettings.setOutputDpi( 96 );
1875 
1876   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
1877   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
1878   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
1879   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
1880   mapSettings.setLabelingEngineSettings( engineSettings );
1881 
1882   QgsMapRendererSequentialJob job( mapSettings );
1883   job.start();
1884   job.waitForFinished();
1885 
1886   std::unique_ptr< QgsLabelingResults > results( job.takeLabelingResults() );
1887   QVERIFY( results );
1888 
1889   // retrieve some labels
1890   QList<QgsLabelPosition> labels = results->labelsAtPosition( QgsPointXY( -654732, 7003282 ) );
1891   QCOMPARE( labels.count(), 1 );
1892   QCOMPARE( labels.at( 0 ).featureId, 1 );
1893   QCOMPARE( labels.at( 0 ).labelText, QStringLiteral( "1" ) );
1894   QGSCOMPARENEAR( labels.at( 0 ).width, 167961, 500 ); // tolerance will probably need tweaking, to account for cross-platform font diffs
1895   QGSCOMPARENEAR( labels.at( 0 ).height, 295119, 500 );
1896   QGSCOMPARENEAR( labels.at( 0 ).labelRect.xMinimum(), -779822, 500 );
1897   QGSCOMPARENEAR( labels.at( 0 ).labelRect.xMaximum(), -611861, 500 );
1898   QGSCOMPARENEAR( labels.at( 0 ).labelRect.yMinimum(), 6897647, 500 );
1899   QGSCOMPARENEAR( labels.at( 0 ).labelRect.yMaximum(), 7192767, 500 );
1900   QCOMPARE( labels.at( 0 ).rotation, 0.0 );
1901 
1902   labels = results->labelsAtPosition( QgsPointXY( -769822, 6927647 ) );
1903   QCOMPARE( labels.count(), 1 );
1904   QCOMPARE( labels.at( 0 ).featureId, 1 );
1905   labels = results->labelsAtPosition( QgsPointXY( -615861, 7132767 ) );
1906   QCOMPARE( labels.count(), 1 );
1907   QCOMPARE( labels.at( 0 ).featureId, 1 );
1908 
1909   labels = results->labelsAtPosition( QgsPointXY( -2463392, 9361711 ) );
1910   QCOMPARE( labels.count(), 1 );
1911   QCOMPARE( labels.at( 0 ).featureId, 2 );
1912   QCOMPARE( labels.at( 0 ).labelText, QStringLiteral( "8888" ) );
1913   QGSCOMPARENEAR( labels.at( 0 ).width, 671844, 500 ); // tolerance will probably need tweaking, to account for cross-platform font diffs
1914   QGSCOMPARENEAR( labels.at( 0 ).height, 295119, 500 );
1915   QGSCOMPARENEAR( labels.at( 0 ).labelRect.xMinimum(), -2779386, 500 );
1916   QGSCOMPARENEAR( labels.at( 0 ).labelRect.xMaximum(), -2107542, 500 );
1917   QGSCOMPARENEAR( labels.at( 0 ).labelRect.yMinimum(), 9240403, 500 );
1918   QGSCOMPARENEAR( labels.at( 0 ).labelRect.yMaximum(), 9535523, 500 );
1919   QCOMPARE( labels.at( 0 ).rotation, 0.0 );
1920   labels = results->labelsAtPosition( QgsPointXY( -1383, 6708478 ) );
1921   QCOMPARE( labels.count(), 1 );
1922   QCOMPARE( labels.at( 0 ).featureId, 3 );
1923   QCOMPARE( labels.at( 0 ).labelText, QStringLiteral( "33333" ) );
1924   QGSCOMPARENEAR( labels.at( 0 ).width, 839805, 500 ); // tolerance will probably need tweaking, to account for cross-platform font diffs
1925   QGSCOMPARENEAR( labels.at( 0 ).height, 295119, 500 );
1926   QGSCOMPARENEAR( labels.at( 0 ).labelRect.xMinimum(), -433112, 500 );
1927   QGSCOMPARENEAR( labels.at( 0 ).labelRect.xMaximum(), 406692, 500 );
1928   QGSCOMPARENEAR( labels.at( 0 ).labelRect.yMinimum(), 6563006, 500 );
1929   QGSCOMPARENEAR( labels.at( 0 ).labelRect.yMaximum(), 6858125, 500 );
1930   QCOMPARE( labels.at( 0 ).rotation, 0.0 );
1931   labels = results->labelsAtPosition( QgsPointXY( -2463392, 6708478 ) );
1932   QCOMPARE( labels.count(), 0 );
1933 
1934   // with rotation
1935   mapSettings.setRotation( 60 );
1936   QgsMapRendererSequentialJob job2( mapSettings );
1937   job2.start();
1938   job2.waitForFinished();
1939   results.reset( job2.takeLabelingResults() );
1940   QVERIFY( results );
1941   labels = results->labelsAtPosition( QgsPointXY( -654732, 7003282 ) );
1942   QCOMPARE( labels.count(), 1 );
1943   QCOMPARE( labels.at( 0 ).featureId, 1 );
1944   QCOMPARE( labels.at( 0 ).labelText, QStringLiteral( "1" ) );
1945   QGSCOMPARENEAR( labels.at( 0 ).width, 167961, 500 ); // tolerance will probably need tweaking, to account for cross-platform font diffs
1946   QGSCOMPARENEAR( labels.at( 0 ).height, 295119, 500 );
1947   QGSCOMPARENEAR( labels.at( 0 ).labelRect.xMinimum(), -865622, 500 );
1948   QGSCOMPARENEAR( labels.at( 0 ).labelRect.xMaximum(), -526060, 500 );
1949   QGSCOMPARENEAR( labels.at( 0 ).labelRect.yMinimum(), 6898697, 500 );
1950   QGSCOMPARENEAR( labels.at( 0 ).labelRect.yMaximum(), 7191716, 500 );
1951   QCOMPARE( labels.at( 0 ).rotation, 60.0 );
1952 
1953   // should fall outside of rotated bounding box!
1954   labels = results->labelsAtPosition( QgsPointXY( -769822, 6927647 ) );
1955   QCOMPARE( labels.count(), 0 );
1956   labels = results->labelsAtPosition( QgsPointXY( -615861, 7132767 ) );
1957   QCOMPARE( labels.count(), 0 );
1958   // just on corner, should only work if rotation of label's bounding box is handled correctly
1959   labels = results->labelsAtPosition( QgsPointXY( -610000, 6898800 ) );
1960   QCOMPARE( labels.count(), 1 );
1961   QCOMPARE( labels.at( 0 ).featureId, 1 );
1962 
1963   labels = results->labelsAtPosition( QgsPointXY( -2463392, 9361711 ) );
1964   QCOMPARE( labels.count(), 1 );
1965   QCOMPARE( labels.at( 0 ).featureId, 2 );
1966   QCOMPARE( labels.at( 0 ).labelText, QStringLiteral( "8888" ) );
1967   QGSCOMPARENEAR( labels.at( 0 ).width, 671844, 500 ); // tolerance will probably need tweaking, to account for cross-platform font diffs
1968   QGSCOMPARENEAR( labels.at( 0 ).height, 295119, 500 );
1969   QGSCOMPARENEAR( labels.at( 0 ).labelRect.xMinimum(), -2739216, 500 );
1970   QGSCOMPARENEAR( labels.at( 0 ).labelRect.xMaximum(), -2147712, 500 );
1971   QGSCOMPARENEAR( labels.at( 0 ).labelRect.yMinimum(), 9023266, 500 );
1972   QGSCOMPARENEAR( labels.at( 0 ).labelRect.yMaximum(), 9752660, 500 );
1973   QCOMPARE( labels.at( 0 ).rotation, 60.0 );
1974   labels = results->labelsAtPosition( QgsPointXY( -1383, 6708478 ) );
1975   QCOMPARE( labels.count(), 1 );
1976   QCOMPARE( labels.at( 0 ).featureId, 3 );
1977   QCOMPARE( labels.at( 0 ).labelText, QStringLiteral( "33333" ) );
1978   QGSCOMPARENEAR( labels.at( 0 ).width, 839805, 500 ); // tolerance will probably need tweaking, to account for cross-platform font diffs
1979   QGSCOMPARENEAR( labels.at( 0 ).height, 295119, 500 );
1980   QGSCOMPARENEAR( labels.at( 0 ).labelRect.xMinimum(), -350952, 500 );
1981   QGSCOMPARENEAR( labels.at( 0 ).labelRect.xMaximum(), 324531, 500 );
1982   QGSCOMPARENEAR( labels.at( 0 ).labelRect.yMinimum(), 6273139, 500 );
1983   QGSCOMPARENEAR( labels.at( 0 ).labelRect.yMaximum(), 7147992, 500 );
1984   QCOMPARE( labels.at( 0 ).rotation, 60.0 );
1985   labels = results->labelsAtPosition( QgsPointXY( -2463392, 6708478 ) );
1986   QCOMPARE( labels.count(), 0 );
1987 }
1988 
pointsetExtend()1989 void TestQgsLabelingEngine::pointsetExtend()
1990 {
1991   // test extending pointsets by distance
1992   {
1993     QVector< double > x;
1994     x << 1 << 9;
1995     QVector< double > y;
1996     y << 2 << 2;
1997     pal::PointSet set( 2, x.data(), y.data() );
1998 
1999     set.extendLineByDistance( 1, 3, 0 );
2000     QCOMPARE( set.getNumPoints(), 4 );
2001     QCOMPARE( set.x.at( 0 ), 0.0 );
2002     QCOMPARE( set.y.at( 0 ), 2.0 );
2003     QCOMPARE( set.x.at( 1 ), 1.0 );
2004     QCOMPARE( set.y.at( 1 ), 2.0 );
2005     QCOMPARE( set.x.at( 2 ), 9.0 );
2006     QCOMPARE( set.y.at( 2 ), 2.0 );
2007     QCOMPARE( set.x.at( 3 ), 12.0 );
2008     QCOMPARE( set.y.at( 3 ), 2.0 );
2009   }
2010 
2011   {
2012     QVector< double > x;
2013     x << 1 << 9;
2014     QVector< double > y;
2015     y << 2 << 2;
2016     pal::PointSet set( 2, x.data(), y.data() );
2017     set.extendLineByDistance( 0, 0, 0 );
2018     QCOMPARE( set.getNumPoints(), 2 );
2019     QCOMPARE( set.x.at( 0 ), 1.0 );
2020     QCOMPARE( set.y.at( 0 ), 2.0 );
2021     QCOMPARE( set.x.at( 1 ), 9.0 );
2022     QCOMPARE( set.y.at( 1 ), 2.0 );
2023   }
2024 
2025   {
2026     pal::PointSet set( 0, nullptr, nullptr );
2027     set.extendLineByDistance( 1, 3, 0 );
2028     QCOMPARE( set.getNumPoints(), 0 );
2029   }
2030 
2031   {
2032     QVector< double > x;
2033     x << 1;
2034     QVector< double > y;
2035     y << 2;
2036     pal::PointSet set( 1, x.data(), y.data() );
2037     set.extendLineByDistance( 1, 3, 0 );
2038     QCOMPARE( set.getNumPoints(), 1 );
2039   }
2040 
2041   {
2042     QVector< double > x;
2043     x << 1 << 2 << 8 << 9;
2044     QVector< double > y;
2045     y << 2 << 3 << 3 << 2;
2046     pal::PointSet set( 4, x.data(), y.data() );
2047     set.extendLineByDistance( 1, 3, 0 );
2048     QCOMPARE( set.getNumPoints(), 6 );
2049     QGSCOMPARENEAR( set.x.at( 0 ), 0.292893, 0.00001 );
2050     QGSCOMPARENEAR( set.y.at( 0 ), 1.29289, 0.00001 );
2051     QCOMPARE( set.x.at( 1 ), 1.0 );
2052     QCOMPARE( set.y.at( 1 ), 2.0 );
2053     QCOMPARE( set.x.at( 2 ), 2.0 );
2054     QCOMPARE( set.y.at( 2 ), 3.0 );
2055     QCOMPARE( set.x.at( 3 ), 8.0 );
2056     QCOMPARE( set.y.at( 3 ), 3.0 );
2057     QCOMPARE( set.x.at( 4 ), 9.0 );
2058     QCOMPARE( set.y.at( 4 ), 2.0 );
2059     QGSCOMPARENEAR( set.x.at( 5 ), 11.121320, 0.00001 );
2060     QGSCOMPARENEAR( set.y.at( 5 ), -0.121320, 0.00001 );
2061   }
2062 
2063   {
2064     QVector< double > x;
2065     x << 9 << 8 << 2 << 1;
2066     QVector< double > y;
2067     y << 2 << 3 << 3 << 2;
2068     pal::PointSet set( 4, x.data(), y.data() );
2069     set.extendLineByDistance( 1, 3, 0 );
2070     QCOMPARE( set.getNumPoints(), 6 );
2071     QGSCOMPARENEAR( set.x.at( 0 ), 9.707107, 0.00001 );
2072     QGSCOMPARENEAR( set.y.at( 0 ), 1.29289, 0.00001 );
2073     QCOMPARE( set.x.at( 1 ), 9.0 );
2074     QCOMPARE( set.y.at( 1 ), 2.0 );
2075     QCOMPARE( set.x.at( 2 ), 8.0 );
2076     QCOMPARE( set.y.at( 2 ), 3.0 );
2077     QCOMPARE( set.x.at( 3 ), 2.0 );
2078     QCOMPARE( set.y.at( 3 ), 3.0 );
2079     QCOMPARE( set.x.at( 4 ), 1.0 );
2080     QCOMPARE( set.y.at( 4 ), 2.0 );
2081     QGSCOMPARENEAR( set.x.at( 5 ), -1.121320, 0.00001 );
2082     QGSCOMPARENEAR( set.y.at( 5 ), -0.121320, 0.00001 );
2083   }
2084 
2085   {
2086     // with averaging
2087     QVector< double > x;
2088     x << 1 << 2 << 8 << 9;
2089     QVector< double > y;
2090     y << 2 << 3 << 3 << 2;
2091     pal::PointSet set( 4, x.data(), y.data() );
2092     set.extendLineByDistance( 1, 3, 0.5 );
2093     QCOMPARE( set.getNumPoints(), 6 );
2094     QGSCOMPARENEAR( set.x.at( 0 ), 0.292893, 0.00001 );
2095     QGSCOMPARENEAR( set.y.at( 0 ), 1.29289, 0.00001 );
2096     QCOMPARE( set.x.at( 1 ), 1.0 );
2097     QCOMPARE( set.y.at( 1 ), 2.0 );
2098     QCOMPARE( set.x.at( 2 ), 2.0 );
2099     QCOMPARE( set.y.at( 2 ), 3.0 );
2100     QCOMPARE( set.x.at( 3 ), 8.0 );
2101     QCOMPARE( set.y.at( 3 ), 3.0 );
2102     QCOMPARE( set.x.at( 4 ), 9.0 );
2103     QCOMPARE( set.y.at( 4 ), 2.0 );
2104     QGSCOMPARENEAR( set.x.at( 5 ), 11.573264, 0.00001 );
2105     QGSCOMPARENEAR( set.y.at( 5 ), 0.457821, 0.00001 );
2106   }
2107 
2108   {
2109     QVector< double > x;
2110     x << 1 << 2 << 8 << 9;
2111     QVector< double > y;
2112     y << 2 << 3 << 3 << 2;
2113     pal::PointSet set( 4, x.data(), y.data() );
2114     set.extendLineByDistance( 1, 3, 1 );
2115     QCOMPARE( set.getNumPoints(), 6 );
2116     QGSCOMPARENEAR( set.x.at( 0 ), 0.292893, 0.00001 );
2117     QGSCOMPARENEAR( set.y.at( 0 ), 1.29289, 0.00001 );
2118     QCOMPARE( set.x.at( 1 ), 1.0 );
2119     QCOMPARE( set.y.at( 1 ), 2.0 );
2120     QCOMPARE( set.x.at( 2 ), 2.0 );
2121     QCOMPARE( set.y.at( 2 ), 3.0 );
2122     QCOMPARE( set.x.at( 3 ), 8.0 );
2123     QCOMPARE( set.y.at( 3 ), 3.0 );
2124     QCOMPARE( set.x.at( 4 ), 9.0 );
2125     QCOMPARE( set.y.at( 4 ), 2.0 );
2126     QGSCOMPARENEAR( set.x.at( 5 ), 11.788722, 0.00001 );
2127     QGSCOMPARENEAR( set.y.at( 5 ), 0.894094, 0.00001 );
2128   }
2129 
2130   {
2131     QVector< double > x;
2132     x << 1 << 2 << 8 << 9;
2133     QVector< double > y;
2134     y << 2 << 3 << 3 << 2;
2135     pal::PointSet set( 4, x.data(), y.data() );
2136     set.extendLineByDistance( 1, 3, 2 );
2137     QCOMPARE( set.getNumPoints(), 6 );
2138     QGSCOMPARENEAR( set.x.at( 0 ), 0.011936, 0.00001 );
2139     QGSCOMPARENEAR( set.y.at( 0 ), 1.845957, 0.00001 );
2140     QCOMPARE( set.x.at( 1 ), 1.0 );
2141     QCOMPARE( set.y.at( 1 ), 2.0 );
2142     QCOMPARE( set.x.at( 2 ), 2.0 );
2143     QCOMPARE( set.y.at( 2 ), 3.0 );
2144     QCOMPARE( set.x.at( 3 ), 8.0 );
2145     QCOMPARE( set.y.at( 3 ), 3.0 );
2146     QCOMPARE( set.x.at( 4 ), 9.0 );
2147     QCOMPARE( set.y.at( 4 ), 2.0 );
2148     QGSCOMPARENEAR( set.x.at( 5 ), 11.917393, 0.00001 );
2149     QGSCOMPARENEAR( set.y.at( 5 ), 1.300845, 0.00001 );
2150   }
2151 
2152   {
2153     QVector< double > x;
2154     x << 1 << 2 << 8 << 9;
2155     QVector< double > y;
2156     y << 2 << 3 << 3 << 2;
2157     pal::PointSet set( 4, x.data(), y.data() );
2158     set.extendLineByDistance( 1, 3, 4 );
2159     QCOMPARE( set.getNumPoints(), 6 );
2160     QGSCOMPARENEAR( set.x.at( 0 ), 0.024713, 0.00001 );
2161     QGSCOMPARENEAR( set.y.at( 0 ), 1.779058, 0.00001 );
2162     QCOMPARE( set.x.at( 1 ), 1.0 );
2163     QCOMPARE( set.y.at( 1 ), 2.0 );
2164     QCOMPARE( set.x.at( 2 ), 2.0 );
2165     QCOMPARE( set.y.at( 2 ), 3.0 );
2166     QCOMPARE( set.x.at( 3 ), 8.0 );
2167     QCOMPARE( set.y.at( 3 ), 3.0 );
2168     QCOMPARE( set.x.at( 4 ), 9.0 );
2169     QCOMPARE( set.y.at( 4 ), 2.0 );
2170     QGSCOMPARENEAR( set.x.at( 5 ), 11.990524, 0.00001 );
2171     QGSCOMPARENEAR( set.y.at( 5 ), 1.761739, 0.00001 );
2172   }
2173 
2174   {
2175     QVector< double > x;
2176     x << 1 << 2 << 8 << 9;
2177     QVector< double > y;
2178     y << 2 << 3 << 3 << 2;
2179     pal::PointSet set( 4, x.data(), y.data() );
2180     set.extendLineByDistance( 1, 3, 5 );
2181     QCOMPARE( set.getNumPoints(), 6 );
2182     QGSCOMPARENEAR( set.x.at( 0 ), 0.040317, 0.00001 );
2183     QGSCOMPARENEAR( set.y.at( 0 ), 1.718915, 0.00001 );
2184     QCOMPARE( set.x.at( 1 ), 1.0 );
2185     QCOMPARE( set.y.at( 1 ), 2.0 );
2186     QCOMPARE( set.x.at( 2 ), 2.0 );
2187     QCOMPARE( set.y.at( 2 ), 3.0 );
2188     QCOMPARE( set.x.at( 3 ), 8.0 );
2189     QCOMPARE( set.y.at( 3 ), 3.0 );
2190     QCOMPARE( set.x.at( 4 ), 9.0 );
2191     QCOMPARE( set.y.at( 4 ), 2.0 );
2192     QGSCOMPARENEAR( set.x.at( 5 ), 11.998204, 0.00001 );
2193     QGSCOMPARENEAR( set.y.at( 5 ),  1.896217, 0.00001 );
2194   }
2195 
2196   {
2197     QVector< double > x;
2198     x << 1 << 2 << 8 << 9;
2199     QVector< double > y;
2200     y << 2 << 3 << 3 << 2;
2201     pal::PointSet set( 4, x.data(), y.data() );
2202     set.extendLineByDistance( 1, 3, 15 );
2203     QCOMPARE( set.getNumPoints(), 6 );
2204     QGSCOMPARENEAR( set.x.at( 0 ), 0.292893, 0.00001 );
2205     QGSCOMPARENEAR( set.y.at( 0 ), 1.292893, 0.00001 );
2206     QCOMPARE( set.x.at( 1 ), 1.0 );
2207     QCOMPARE( set.y.at( 1 ), 2.0 );
2208     QCOMPARE( set.x.at( 2 ), 2.0 );
2209     QCOMPARE( set.y.at( 2 ), 3.0 );
2210     QCOMPARE( set.x.at( 3 ), 8.0 );
2211     QCOMPARE( set.y.at( 3 ), 3.0 );
2212     QCOMPARE( set.x.at( 4 ), 9.0 );
2213     QCOMPARE( set.y.at( 4 ), 2.0 );
2214     QGSCOMPARENEAR( set.x.at( 5 ), 11.982541, 0.00001 );
2215     QGSCOMPARENEAR( set.y.at( 5 ), 1.676812, 0.00001 );
2216   }
2217 }
2218 
curvedOverrun()2219 void TestQgsLabelingEngine::curvedOverrun()
2220 {
2221   // test a small line with curved labels allows overruns when specified
2222   QgsPalLayerSettings settings;
2223   setDefaultLabelParams( settings );
2224 
2225   QgsTextFormat format = settings.format();
2226   format.setSize( 20 );
2227   format.setColor( QColor( 0, 0, 0 ) );
2228   settings.setFormat( format );
2229 
2230   settings.fieldName = QStringLiteral( "'XXXXXXX'" );
2231   settings.isExpression = true;
2232   settings.placement = QgsPalLayerSettings::Curved;
2233   settings.labelPerPart = false;
2234   settings.lineSettings().setOverrunDistance( 0 );
2235 
2236   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
2237   vl2->setRenderer( new QgsNullSymbolRenderer() );
2238 
2239   QgsFeature f;
2240   f.setAttributes( QgsAttributes() << 1 );
2241   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190079.9 5000000.3, 190080 5000000, 190085 5000005, 190110 5000005)" ) ) );
2242   QVERIFY( vl2->dataProvider()->addFeature( f ) );
2243 
2244   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2245   vl2->setLabelsEnabled( true );
2246 
2247   // make a fake render context
2248   QSize size( 640, 480 );
2249   QgsMapSettings mapSettings;
2250   mapSettings.setDestinationCrs( vl2->crs() );
2251 
2252   mapSettings.setOutputSize( size );
2253   mapSettings.setExtent( QgsRectangle( 190000, 5000000, 190200, 5000010 ) );
2254   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
2255   mapSettings.setOutputDpi( 96 );
2256 
2257   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
2258   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
2259   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
2260   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
2261   mapSettings.setLabelingEngineSettings( engineSettings );
2262 
2263   QgsMapRendererSequentialJob job( mapSettings );
2264   job.start();
2265   job.waitForFinished();
2266 
2267   QImage img = job.renderedImage();
2268   QVERIFY( imageCheck( QStringLiteral( "label_curved_no_overrun" ), img, 20 ) );
2269 
2270   settings.lineSettings().setOverrunDistance( 11 );
2271   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2272   vl2->setLabelsEnabled( true );
2273   QgsMapRendererSequentialJob job2( mapSettings );
2274   job2.start();
2275   job2.waitForFinished();
2276 
2277   img = job2.renderedImage();
2278   QVERIFY( imageCheck( QStringLiteral( "label_curved_overrun" ), img, 20 ) );
2279 
2280   // too short for what's required...
2281   settings.lineSettings().setOverrunDistance( 3 );
2282   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2283   vl2->setLabelsEnabled( true );
2284   QgsMapRendererSequentialJob job3( mapSettings );
2285   job3.start();
2286   job3.waitForFinished();
2287 
2288   img = job3.renderedImage();
2289   QVERIFY( imageCheck( QStringLiteral( "label_curved_no_overrun" ), img, 20 ) );
2290 }
2291 
parallelOverrun()2292 void TestQgsLabelingEngine::parallelOverrun()
2293 {
2294   // test a small line with curved labels allows overruns when specified
2295   QgsPalLayerSettings settings;
2296   setDefaultLabelParams( settings );
2297 
2298   QgsTextFormat format = settings.format();
2299   format.setSize( 20 );
2300   format.setColor( QColor( 0, 0, 0 ) );
2301   settings.setFormat( format );
2302 
2303   settings.fieldName = QStringLiteral( "'XXXXXXX'" );
2304   settings.isExpression = true;
2305   settings.placement = QgsPalLayerSettings::Line;
2306   settings.labelPerPart = false;
2307   settings.lineSettings().setOverrunDistance( 0 );
2308 
2309   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
2310   vl2->setRenderer( new QgsNullSymbolRenderer() );
2311 
2312   QgsFeature f;
2313   f.setAttributes( QgsAttributes() << 1 );
2314   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190079.9 5000000.3, 190080 5000000, 190085 5000005, 190110 5000005)" ) ) );
2315   QVERIFY( vl2->dataProvider()->addFeature( f ) );
2316 
2317   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2318   vl2->setLabelsEnabled( true );
2319 
2320   // make a fake render context
2321   QSize size( 640, 480 );
2322   QgsMapSettings mapSettings;
2323   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
2324   mapSettings.setDestinationCrs( vl2->crs() );
2325 
2326   mapSettings.setOutputSize( size );
2327   mapSettings.setExtent( QgsRectangle( 190000, 5000000, 190200, 5000010 ) );
2328   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
2329   mapSettings.setOutputDpi( 96 );
2330 
2331   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
2332   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
2333   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
2334   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
2335   mapSettings.setLabelingEngineSettings( engineSettings );
2336 
2337   QgsMapRendererSequentialJob job( mapSettings );
2338   job.start();
2339   job.waitForFinished();
2340 
2341   QImage img = job.renderedImage();
2342   QVERIFY( imageCheck( QStringLiteral( "label_curved_no_overrun" ), img, 20 ) );
2343 
2344   settings.lineSettings().setOverrunDistance( 10 );
2345   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2346   vl2->setLabelsEnabled( true );
2347   QgsMapRendererSequentialJob job2( mapSettings );
2348   job2.start();
2349   job2.waitForFinished();
2350 
2351   img = job2.renderedImage();
2352   QVERIFY( imageCheck( QStringLiteral( "label_parallel_overrun" ), img, 20 ) );
2353 
2354   // too short for what's required...
2355   settings.lineSettings().setOverrunDistance( 3 );
2356   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2357   vl2->setLabelsEnabled( true );
2358   QgsMapRendererSequentialJob job3( mapSettings );
2359   job3.start();
2360   job3.waitForFinished();
2361 
2362   img = job3.renderedImage();
2363   QVERIFY( imageCheck( QStringLiteral( "label_curved_no_overrun" ), img, 20 ) );
2364 }
2365 
testDataDefinedLabelAllParts()2366 void TestQgsLabelingEngine::testDataDefinedLabelAllParts()
2367 {
2368   // test a small line with curved labels allows overruns when specified
2369   QgsPalLayerSettings settings;
2370   setDefaultLabelParams( settings );
2371 
2372   QgsTextFormat format = settings.format();
2373   format.setSize( 20 );
2374   format.setColor( QColor( 0, 0, 0 ) );
2375   settings.setFormat( format );
2376 
2377   settings.fieldName = QStringLiteral( "'X'" );
2378   settings.isExpression = true;
2379   settings.placement = QgsPalLayerSettings::OverPoint;
2380   settings.labelPerPart = false;
2381 
2382   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "MultiPolygon?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
2383   vl2->setRenderer( new QgsNullSymbolRenderer() );
2384 
2385   QgsFeature f;
2386   f.setAttributes( QgsAttributes() << 1 );
2387   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "MultiPoint (190030 5000000, 190080 5000000, 190084 5000000 )" ) ).buffer( 10, 5 ) );
2388   QVERIFY( vl2->dataProvider()->addFeature( f ) );
2389 
2390   f.setAttributes( QgsAttributes() << 2 );
2391   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "MultiPoint (190030 5000060, 190080 5000060, 190084 5000060 )" ) ).buffer( 10, 5 ) );
2392   QVERIFY( vl2->dataProvider()->addFeature( f ) );
2393 
2394   settings.dataDefinedProperties().setProperty( QgsPalLayerSettings::LabelAllParts, QgsProperty::fromExpression( QStringLiteral( "\"id\" = 2" ) ) );
2395 
2396   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2397   vl2->setLabelsEnabled( true );
2398 
2399   // make a fake render context
2400   QSize size( 640, 480 );
2401   QgsMapSettings mapSettings;
2402   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
2403   mapSettings.setDestinationCrs( vl2->crs() );
2404 
2405   mapSettings.setOutputSize( size );
2406   mapSettings.setExtent( QgsRectangle( 190000, 5000000, 190200, 5000010 ) );
2407   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
2408   mapSettings.setOutputDpi( 96 );
2409 
2410   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
2411   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
2412   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
2413   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
2414   mapSettings.setLabelingEngineSettings( engineSettings );
2415 
2416   QgsMapRendererSequentialJob job( mapSettings );
2417   job.start();
2418   job.waitForFinished();
2419 
2420   QImage img = job.renderedImage();
2421   QVERIFY( imageCheck( QStringLiteral( "label_datadefined_label_all_parts" ), img, 20 ) );
2422 
2423 }
2424 
testVerticalOrientation()2425 void TestQgsLabelingEngine::testVerticalOrientation()
2426 {
2427   QSize size( 640, 480 );
2428   QgsMapSettings mapSettings;
2429   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
2430   mapSettings.setOutputSize( size );
2431   mapSettings.setExtent( vl->extent() );
2432   mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
2433   mapSettings.setOutputDpi( 96 );
2434 
2435   // first render the map and labeling separately
2436 
2437   QgsMapRendererSequentialJob job( mapSettings );
2438   job.start();
2439   job.waitForFinished();
2440 
2441   QImage img = job.renderedImage();
2442 
2443   QPainter p( &img );
2444   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
2445   context.setPainter( &p );
2446 
2447   QgsPalLayerSettings settings;
2448   settings.fieldName = QStringLiteral( "Class" );
2449   setDefaultLabelParams( settings );
2450   QgsTextFormat format = settings.format();
2451   format.setOrientation( QgsTextFormat::VerticalOrientation );
2452   settings.setFormat( format );
2453 
2454   vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2455   vl->setLabelsEnabled( true );
2456 
2457   QgsDefaultLabelingEngine engine;
2458   engine.setMapSettings( mapSettings );
2459   engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
2460   //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
2461   engine.run( context );
2462 
2463   p.end();
2464 
2465   QVERIFY( imageCheck( "labeling_vertical", img, 20 ) );
2466 
2467   vl->setLabeling( nullptr );
2468 }
2469 
testVerticalOrientationLetterLineSpacing()2470 void TestQgsLabelingEngine::testVerticalOrientationLetterLineSpacing()
2471 {
2472   QSize size( 640, 480 );
2473   QgsMapSettings mapSettings;
2474   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
2475   mapSettings.setOutputSize( size );
2476   mapSettings.setExtent( vl->extent() );
2477   mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
2478   mapSettings.setOutputDpi( 96 );
2479 
2480   // first render the map and labeling separately
2481 
2482   QgsMapRendererSequentialJob job( mapSettings );
2483   job.start();
2484   job.waitForFinished();
2485 
2486   QImage img = job.renderedImage();
2487 
2488   QPainter p( &img );
2489   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
2490   context.setPainter( &p );
2491 
2492   QgsPalLayerSettings settings;
2493   settings.fieldName = QStringLiteral( "\"Class\" || '\n' || \"Heading\"" );
2494   settings.isExpression = true;
2495   setDefaultLabelParams( settings );
2496   QgsTextFormat format = settings.format();
2497   format.setOrientation( QgsTextFormat::VerticalOrientation );
2498   format.setLineHeight( 1.5 );
2499   QFont font = format.font();
2500   font.setLetterSpacing( QFont::AbsoluteSpacing, 3.75 );
2501   format.setFont( font );
2502   settings.setFormat( format );
2503 
2504   vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2505   vl->setLabelsEnabled( true );
2506 
2507   QgsDefaultLabelingEngine engine;
2508   engine.setMapSettings( mapSettings );
2509   engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
2510   //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
2511   engine.run( context );
2512 
2513   p.end();
2514 
2515   QVERIFY( imageCheck( "labeling_vertical_letter_line_spacing", img, 20 ) );
2516 
2517   vl->setLabeling( nullptr );
2518 }
2519 
testRotationBasedOrientationPoint()2520 void TestQgsLabelingEngine::testRotationBasedOrientationPoint()
2521 {
2522   QSize size( 640, 480 );
2523   QgsMapSettings mapSettings;
2524   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
2525   mapSettings.setOutputSize( size );
2526   mapSettings.setExtent( vl->extent() );
2527   mapSettings.setLayers( QList<QgsMapLayer *>() << vl );
2528   mapSettings.setOutputDpi( 96 );
2529 
2530   // first render the map and labeling separately
2531 
2532   QgsMapRendererSequentialJob job( mapSettings );
2533   job.start();
2534   job.waitForFinished();
2535 
2536   QImage img = job.renderedImage();
2537 
2538   QPainter p( &img );
2539   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
2540   context.setPainter( &p );
2541 
2542   QgsPalLayerSettings settings;
2543   settings.fieldName = QStringLiteral( "Class" );
2544   setDefaultLabelParams( settings );
2545   settings.dataDefinedProperties().setProperty( QgsPalLayerSettings::LabelRotation, QgsProperty::fromExpression( QStringLiteral( "\"Heading\"" ) ) );
2546   QgsTextFormat format = settings.format();
2547   format.setOrientation( QgsTextFormat::RotationBasedOrientation );
2548   settings.setFormat( format );
2549 
2550   vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2551   vl->setLabelsEnabled( true );
2552 
2553   QgsDefaultLabelingEngine engine;
2554   engine.setMapSettings( mapSettings );
2555   engine.addProvider( new QgsVectorLayerLabelProvider( vl, QString(), true, &settings ) );
2556   //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
2557   engine.run( context );
2558 
2559   p.end();
2560 
2561   QVERIFY( imageCheck( "labeling_rotation_based_orientation_point", img, 20 ) );
2562 
2563   vl->setLabeling( nullptr );
2564 }
2565 
testRotationBasedOrientationLine()2566 void TestQgsLabelingEngine::testRotationBasedOrientationLine()
2567 {
2568   QString filename = QStringLiteral( TEST_DATA_DIR ) + "/lines.shp";
2569   QgsVectorLayer *vl2 = new QgsVectorLayer( filename, QStringLiteral( "lines" ), QStringLiteral( "ogr" ) );
2570   QVERIFY( vl2->isValid() );
2571   QgsProject::instance()->addMapLayer( vl2 );
2572 
2573   QSize size( 640, 480 );
2574   QgsMapSettings mapSettings;
2575   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
2576   mapSettings.setOutputSize( size );
2577   mapSettings.setExtent( vl2->extent() );
2578   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2 );
2579   mapSettings.setOutputDpi( 96 );
2580 
2581   // first render the map and labeling separately
2582 
2583   QgsMapRendererSequentialJob job( mapSettings );
2584   job.start();
2585   job.waitForFinished();
2586 
2587   QImage img = job.renderedImage();
2588 
2589   QPainter p( &img );
2590   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
2591   context.setPainter( &p );
2592 
2593   QgsPalLayerSettings settings;
2594   settings.fieldName = QStringLiteral( "'1234'" );
2595   settings.isExpression = true;
2596   setDefaultLabelParams( settings );
2597   settings.placement = QgsPalLayerSettings::Line;
2598   settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine );
2599   QgsTextFormat format = settings.format();
2600   format.setOrientation( QgsTextFormat::RotationBasedOrientation );
2601   settings.setFormat( format );
2602 
2603   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2604   vl2->setLabelsEnabled( true );
2605 
2606   QgsDefaultLabelingEngine engine;
2607   engine.setMapSettings( mapSettings );
2608   engine.addProvider( new QgsVectorLayerLabelProvider( vl2, QString(), true, &settings ) );
2609   //engine.setFlags( QgsLabelingEngine::RenderOutlineLabels | QgsLabelingEngine::DrawLabelRectOnly );
2610   engine.run( context );
2611 
2612   p.end();
2613 
2614   QVERIFY( imageCheck( "labeling_rotation_based_orientation_line", img, 20 ) );
2615 
2616   vl2->setLabeling( nullptr );
2617   QgsProject::instance()->removeMapLayer( vl2 );
2618 }
2619 
testMapUnitLetterSpacing()2620 void TestQgsLabelingEngine::testMapUnitLetterSpacing()
2621 {
2622   QgsPalLayerSettings settings;
2623   setDefaultLabelParams( settings );
2624 
2625   QgsTextFormat format = settings.format();
2626   format.setSize( 50 );
2627   format.setSizeUnit( QgsUnitTypes::RenderMapUnits );
2628   format.setColor( QColor( 0, 0, 0 ) );
2629   settings.setFormat( format );
2630 
2631   settings.fieldName = QStringLiteral( "'XX'" );
2632   settings.isExpression = true;
2633   settings.placement = QgsPalLayerSettings::Line;
2634   QFont font = format.font();
2635   font.setLetterSpacing( QFont::AbsoluteSpacing, 30 );
2636   format.setFont( font );
2637   settings.setFormat( format );
2638 
2639   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
2640   vl2->setRenderer( new QgsNullSymbolRenderer() );
2641 
2642   QgsFeature f;
2643   f.setAttributes( QgsAttributes() << 1 );
2644   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190020 5000000, 190180 5000000)" ) ) );
2645   QVERIFY( vl2->dataProvider()->addFeature( f ) );
2646 
2647   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2648   vl2->setLabelsEnabled( true );
2649 
2650   // make a fake render context
2651   QSize size( 640, 480 );
2652   QgsMapSettings mapSettings;
2653   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
2654   mapSettings.setDestinationCrs( vl2->crs() );
2655 
2656   mapSettings.setOutputSize( size );
2657   mapSettings.setExtent( QgsRectangle( 190000, 5000000, 190200, 5000010 ) );
2658   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
2659   mapSettings.setOutputDpi( 96 );
2660 
2661   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
2662   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
2663   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
2664   mapSettings.setLabelingEngineSettings( engineSettings );
2665 
2666   QgsMapRendererSequentialJob job( mapSettings );
2667   job.start();
2668   job.waitForFinished();
2669 
2670   QImage img = job.renderedImage();
2671   QVERIFY( imageCheck( QStringLiteral( "label_letter_spacing_map_units" ), img, 20 ) );
2672 }
2673 
testMapUnitWordSpacing()2674 void TestQgsLabelingEngine::testMapUnitWordSpacing()
2675 {
2676   QgsPalLayerSettings settings;
2677   setDefaultLabelParams( settings );
2678 
2679   QgsTextFormat format = settings.format();
2680   format.setSize( 50 );
2681   format.setSizeUnit( QgsUnitTypes::RenderMapUnits );
2682   format.setColor( QColor( 0, 0, 0 ) );
2683   settings.setFormat( format );
2684 
2685   settings.fieldName = QStringLiteral( "'X X'" );
2686   settings.isExpression = true;
2687   settings.placement = QgsPalLayerSettings::Line;
2688   QFont font = format.font();
2689   font.setWordSpacing( 30 );
2690   format.setFont( font );
2691   settings.setFormat( format );
2692 
2693   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
2694   vl2->setRenderer( new QgsNullSymbolRenderer() );
2695 
2696   QgsFeature f;
2697   f.setAttributes( QgsAttributes() << 1 );
2698   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190020 5000000, 190180 5000000)" ) ) );
2699   QVERIFY( vl2->dataProvider()->addFeature( f ) );
2700 
2701   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2702   vl2->setLabelsEnabled( true );
2703 
2704   // make a fake render context
2705   QSize size( 640, 480 );
2706   QgsMapSettings mapSettings;
2707   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
2708   mapSettings.setDestinationCrs( vl2->crs() );
2709 
2710   mapSettings.setOutputSize( size );
2711   mapSettings.setExtent( QgsRectangle( 190000, 5000000, 190200, 5000010 ) );
2712   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
2713   mapSettings.setOutputDpi( 96 );
2714 
2715   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
2716   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
2717   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
2718   mapSettings.setLabelingEngineSettings( engineSettings );
2719 
2720   QgsMapRendererSequentialJob job( mapSettings );
2721   job.start();
2722   job.waitForFinished();
2723 
2724   QImage img = job.renderedImage();
2725   QVERIFY( imageCheck( QStringLiteral( "label_word_spacing_map_units" ), img, 20 ) );
2726 }
2727 
testReferencedFields()2728 void TestQgsLabelingEngine::testReferencedFields()
2729 {
2730   QgsPalLayerSettings settings;
2731   settings.fieldName = QStringLiteral( "hello+world" );
2732   settings.isExpression = false;
2733 
2734   QCOMPARE( settings.referencedFields( QgsRenderContext() ), QSet<QString>() << QStringLiteral( "hello+world" ) );
2735 
2736   settings.isExpression = true;
2737 
2738   QCOMPARE( settings.referencedFields( QgsRenderContext() ), QSet<QString>() << QStringLiteral( "hello" ) << QStringLiteral( "world" ) );
2739 
2740   settings.dataDefinedProperties().setProperty( QgsPalLayerSettings::Size, QgsProperty::fromField( QStringLiteral( "my_dd_size" ) ) );
2741 
2742   QCOMPARE( settings.referencedFields( QgsRenderContext() ), QSet<QString>() << QStringLiteral( "hello" ) << QStringLiteral( "world" ) << QStringLiteral( "my_dd_size" ) );
2743 }
2744 
testClipping()2745 void TestQgsLabelingEngine::testClipping()
2746 {
2747   QgsPalLayerSettings settings;
2748   setDefaultLabelParams( settings );
2749 
2750   QgsTextFormat format = settings.format();
2751   format.setSize( 12 );
2752   format.setSizeUnit( QgsUnitTypes::RenderPoints );
2753   format.setColor( QColor( 0, 0, 0 ) );
2754   settings.setFormat( format );
2755 
2756   settings.fieldName = QStringLiteral( "Name" );
2757   settings.placement = QgsPalLayerSettings::Line;
2758 
2759   const QString filename = QStringLiteral( TEST_DATA_DIR ) + "/lines.shp";
2760   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( filename, QStringLiteral( "lines" ), QStringLiteral( "ogr" ) ) );
2761 
2762   QgsStringMap props;
2763   props.insert( QStringLiteral( "outline_color" ), QStringLiteral( "#487bb6" ) );
2764   props.insert( QStringLiteral( "outline_width" ), QStringLiteral( "1" ) );
2765   std::unique_ptr< QgsLineSymbol > symbol( QgsLineSymbol::createSimple( props ) );
2766   vl2->setRenderer( new QgsSingleSymbolRenderer( symbol.release() ) );
2767 
2768   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2769   vl2->setLabelsEnabled( true );
2770 
2771   // make a fake render context
2772   QSize size( 640, 480 );
2773   QgsMapSettings mapSettings;
2774   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
2775   mapSettings.setDestinationCrs( vl2->crs() );
2776 
2777   mapSettings.setOutputSize( size );
2778   mapSettings.setExtent( QgsRectangle( -117.543, 49.438, -82.323, 21.839 ) );
2779   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
2780   mapSettings.setOutputDpi( 96 );
2781 
2782   QgsMapClippingRegion region1( QgsGeometry::fromWkt( "Polygon ((-92 45, -99 36, -94 29, -82 29, -81 45, -92 45))" ) );
2783   region1.setFeatureClip( QgsMapClippingRegion::FeatureClippingType::ClipToIntersection );
2784   mapSettings.addClippingRegion( region1 );
2785 
2786   QgsMapClippingRegion region2( QgsGeometry::fromWkt( "Polygon ((-85 36, -85 46, -107 47, -108 28, -85 28, -85 36))" ) );
2787   region2.setFeatureClip( QgsMapClippingRegion::FeatureClippingType::ClipPainterOnly );
2788   mapSettings.addClippingRegion( region2 );
2789 
2790   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
2791   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
2792   //engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
2793   mapSettings.setLabelingEngineSettings( engineSettings );
2794 
2795   QgsMapRendererSequentialJob job( mapSettings );
2796   job.start();
2797   job.waitForFinished();
2798 
2799   QImage img = job.renderedImage();
2800   QVERIFY( imageCheck( QStringLiteral( "label_feature_clipping" ), img, 20 ) );
2801 
2802   // also check with symbol levels
2803   vl2->renderer()->setUsingSymbolLevels( true );
2804   QgsMapRendererSequentialJob job2( mapSettings );
2805   job2.start();
2806   job2.waitForFinished();
2807 
2808   img = job2.renderedImage();
2809   QVERIFY( imageCheck( QStringLiteral( "label_feature_clipping" ), img, 20 ) );
2810 }
2811 
testLineAnchorParallel()2812 void TestQgsLabelingEngine::testLineAnchorParallel()
2813 {
2814   // test line label anchor with parallel labels
2815   QgsPalLayerSettings settings;
2816   setDefaultLabelParams( settings );
2817 
2818   QgsTextFormat format = settings.format();
2819   format.setSize( 20 );
2820   format.setColor( QColor( 0, 0, 0 ) );
2821   settings.setFormat( format );
2822 
2823   settings.fieldName = QStringLiteral( "'XXXXXXXX'" );
2824   settings.isExpression = true;
2825   settings.placement = QgsPalLayerSettings::Line;
2826   settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine );
2827   settings.labelPerPart = false;
2828   settings.lineSettings().setLineAnchorPercent( 0.0 );
2829 
2830   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
2831   vl2->setRenderer( new QgsNullSymbolRenderer() );
2832 
2833   QgsFeature f;
2834   f.setAttributes( QgsAttributes() << 1 );
2835   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190000 5000010, 190200 5000000)" ) ) );
2836   QVERIFY( vl2->dataProvider()->addFeature( f ) );
2837 
2838   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2839   vl2->setLabelsEnabled( true );
2840 
2841   // make a fake render context
2842   QSize size( 640, 480 );
2843   QgsMapSettings mapSettings;
2844   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
2845   mapSettings.setDestinationCrs( vl2->crs() );
2846 
2847   mapSettings.setOutputSize( size );
2848   mapSettings.setExtent( f.geometry().boundingBox().buffered( 10 ) );
2849   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
2850   mapSettings.setOutputDpi( 96 );
2851 
2852   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
2853   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
2854   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
2855   // engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
2856   mapSettings.setLabelingEngineSettings( engineSettings );
2857 
2858   QgsMapRendererSequentialJob job( mapSettings );
2859   job.start();
2860   job.waitForFinished();
2861 
2862   QImage img = job.renderedImage();
2863   QVERIFY( imageCheck( QStringLiteral( "parallel_anchor_start" ), img, 20 ) );
2864 
2865   settings.lineSettings().setLineAnchorPercent( 1.0 );
2866   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
2867   QgsMapRendererSequentialJob job2( mapSettings );
2868   job2.start();
2869   job2.waitForFinished();
2870 
2871   img = job2.renderedImage();
2872   QVERIFY( imageCheck( QStringLiteral( "parallel_anchor_end" ), img, 20 ) );
2873 }
2874 
testLineAnchorParallelConstraints()2875 void TestQgsLabelingEngine::testLineAnchorParallelConstraints()
2876 {
2877   // test line label anchor with parallel labels
2878   QgsPalLayerSettings settings;
2879   setDefaultLabelParams( settings );
2880 
2881   QgsTextFormat format = settings.format();
2882   format.setSize( 20 );
2883   format.setColor( QColor( 0, 0, 0 ) );
2884   settings.setFormat( format );
2885 
2886   settings.fieldName = QStringLiteral( "'XXXXXXXX'" );
2887   settings.isExpression = true;
2888   settings.placement = QgsPalLayerSettings::Line;
2889   settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine );
2890   settings.labelPerPart = false;
2891   settings.lineSettings().setLineAnchorPercent( 0.0 );
2892   settings.lineSettings().setAnchorType( QgsLabelLineSettings::AnchorType::HintOnly );
2893 
2894   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
2895   vl2->setRenderer( new QgsNullSymbolRenderer() );
2896 
2897   QgsFeature f;
2898   f.setAttributes( QgsAttributes() << 1 );
2899   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190000 5000000, 190010 5000010, 190190 5000010, 190200 5000000)" ) ) );
2900   QVERIFY( vl2->dataProvider()->addFeature( f ) );
2901 
2902   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2903   vl2->setLabelsEnabled( true );
2904 
2905   // make a fake render context
2906   QSize size( 640, 480 );
2907   QgsMapSettings mapSettings;
2908   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
2909   mapSettings.setDestinationCrs( vl2->crs() );
2910 
2911   mapSettings.setOutputSize( size );
2912   mapSettings.setExtent( f.geometry().boundingBox().buffered( 10 ) );
2913   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
2914   mapSettings.setOutputDpi( 96 );
2915 
2916   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
2917   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
2918   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
2919   // engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
2920   mapSettings.setLabelingEngineSettings( engineSettings );
2921 
2922   QgsMapRendererSequentialJob job( mapSettings );
2923   job.start();
2924   job.waitForFinished();
2925 
2926   QImage img = job.renderedImage();
2927   QVERIFY( imageCheck( QStringLiteral( "parallel_hint_anchor_start" ), img, 20 ) );
2928 
2929   settings.lineSettings().setLineAnchorPercent( 1.0 );
2930   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
2931   QgsMapRendererSequentialJob job2( mapSettings );
2932   job2.start();
2933   job2.waitForFinished();
2934 
2935   img = job2.renderedImage();
2936   QVERIFY( imageCheck( QStringLiteral( "parallel_hint_anchor_end" ), img, 20 ) );
2937 
2938   settings.lineSettings().setAnchorType( QgsLabelLineSettings::AnchorType::Strict );
2939   settings.lineSettings().setLineAnchorPercent( 0.0 );
2940   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
2941   QgsMapRendererSequentialJob job3( mapSettings );
2942   job3.start();
2943   job3.waitForFinished();
2944 
2945   img = job3.renderedImage();
2946   QVERIFY( imageCheck( QStringLiteral( "parallel_strict_anchor_start" ), img, 20 ) );
2947 
2948   settings.lineSettings().setLineAnchorPercent( 1.0 );
2949   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
2950   QgsMapRendererSequentialJob job4( mapSettings );
2951   job4.start();
2952   job4.waitForFinished();
2953 
2954   img = job4.renderedImage();
2955   QVERIFY( imageCheck( QStringLiteral( "parallel_strict_anchor_end" ), img, 20 ) );
2956 }
2957 
testLineAnchorCurved()2958 void TestQgsLabelingEngine::testLineAnchorCurved()
2959 {
2960   // test line label anchor with curved labels
2961   QgsPalLayerSettings settings;
2962   setDefaultLabelParams( settings );
2963 
2964   QgsTextFormat format = settings.format();
2965   format.setSize( 20 );
2966   format.setColor( QColor( 0, 0, 0 ) );
2967   settings.setFormat( format );
2968 
2969   settings.fieldName = QStringLiteral( "'XXXXXXXX'" );
2970   settings.isExpression = true;
2971   settings.placement = QgsPalLayerSettings::Curved;
2972   settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine );
2973   settings.labelPerPart = false;
2974   settings.lineSettings().setLineAnchorPercent( 0.0 );
2975 
2976   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
2977   vl2->setRenderer( new QgsNullSymbolRenderer() );
2978 
2979   QgsFeature f;
2980   f.setAttributes( QgsAttributes() << 1 );
2981   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190000 5000010, 190200 5000000)" ) ) );
2982   QVERIFY( vl2->dataProvider()->addFeature( f ) );
2983 
2984   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
2985   vl2->setLabelsEnabled( true );
2986 
2987   // make a fake render context
2988   QSize size( 640, 480 );
2989   QgsMapSettings mapSettings;
2990   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
2991   mapSettings.setDestinationCrs( vl2->crs() );
2992 
2993   mapSettings.setOutputSize( size );
2994   mapSettings.setExtent( f.geometry().boundingBox().buffered( 10 ) );
2995   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
2996   mapSettings.setOutputDpi( 96 );
2997 
2998   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
2999   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
3000   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
3001   // engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
3002   mapSettings.setLabelingEngineSettings( engineSettings );
3003 
3004   QgsMapRendererSequentialJob job( mapSettings );
3005   job.start();
3006   job.waitForFinished();
3007 
3008   QImage img = job.renderedImage();
3009   QVERIFY( imageCheck( QStringLiteral( "curved_anchor_start" ), img, 20 ) );
3010 
3011   settings.lineSettings().setLineAnchorPercent( 1.0 );
3012   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
3013   QgsMapRendererSequentialJob job2( mapSettings );
3014   job2.start();
3015   job2.waitForFinished();
3016 
3017   img = job2.renderedImage();
3018   QVERIFY( imageCheck( QStringLiteral( "curved_anchor_end" ), img, 20 ) );
3019 }
3020 
testLineAnchorCurvedConstraints()3021 void TestQgsLabelingEngine::testLineAnchorCurvedConstraints()
3022 {
3023   // test line label anchor with parallel labels
3024   QgsPalLayerSettings settings;
3025   setDefaultLabelParams( settings );
3026 
3027   QgsTextFormat format = settings.format();
3028   format.setSize( 20 );
3029   format.setColor( QColor( 0, 0, 0 ) );
3030   settings.setFormat( format );
3031 
3032   settings.fieldName = QStringLiteral( "'XXXXXXXX'" );
3033   settings.isExpression = true;
3034   settings.placement = QgsPalLayerSettings::Curved;
3035   settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine );
3036   settings.labelPerPart = false;
3037   settings.lineSettings().setLineAnchorPercent( 0.0 );
3038   settings.lineSettings().setAnchorType( QgsLabelLineSettings::AnchorType::HintOnly );
3039 
3040   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
3041   vl2->setRenderer( new QgsNullSymbolRenderer() );
3042 
3043   QgsFeature f;
3044   f.setAttributes( QgsAttributes() << 1 );
3045   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190000 5000000, 190010 5000010, 190190 5000010, 190200 5000000)" ) ) );
3046   QVERIFY( vl2->dataProvider()->addFeature( f ) );
3047 
3048   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
3049   vl2->setLabelsEnabled( true );
3050 
3051   // make a fake render context
3052   QSize size( 640, 480 );
3053   QgsMapSettings mapSettings;
3054   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
3055   mapSettings.setDestinationCrs( vl2->crs() );
3056 
3057   mapSettings.setOutputSize( size );
3058   mapSettings.setExtent( f.geometry().boundingBox().buffered( 10 ) );
3059   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
3060   mapSettings.setOutputDpi( 96 );
3061 
3062   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
3063   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
3064   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
3065   // engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
3066   mapSettings.setLabelingEngineSettings( engineSettings );
3067 
3068   QgsMapRendererSequentialJob job( mapSettings );
3069   job.start();
3070   job.waitForFinished();
3071 
3072   QImage img = job.renderedImage();
3073   QVERIFY( imageCheck( QStringLiteral( "curved_hint_anchor_start" ), img, 20 ) );
3074 
3075   settings.lineSettings().setLineAnchorPercent( 1.0 );
3076   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
3077   QgsMapRendererSequentialJob job2( mapSettings );
3078   job2.start();
3079   job2.waitForFinished();
3080 
3081   img = job2.renderedImage();
3082   QVERIFY( imageCheck( QStringLiteral( "curved_hint_anchor_end" ), img, 20 ) );
3083 
3084   settings.lineSettings().setAnchorType( QgsLabelLineSettings::AnchorType::Strict );
3085   settings.lineSettings().setLineAnchorPercent( 0.0 );
3086   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
3087   QgsMapRendererSequentialJob job3( mapSettings );
3088   job3.start();
3089   job3.waitForFinished();
3090 
3091   img = job3.renderedImage();
3092   QVERIFY( imageCheck( QStringLiteral( "curved_strict_anchor_start" ), img, 20 ) );
3093 
3094   settings.lineSettings().setLineAnchorPercent( 1.0 );
3095   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
3096   QgsMapRendererSequentialJob job4( mapSettings );
3097   job4.start();
3098   job4.waitForFinished();
3099 
3100   img = job4.renderedImage();
3101   QVERIFY( imageCheck( QStringLiteral( "curved_strict_anchor_end" ), img, 20 ) );
3102 }
3103 
testLineAnchorHorizontal()3104 void TestQgsLabelingEngine::testLineAnchorHorizontal()
3105 {
3106   // test line label anchor with horizontal labels
3107   QgsPalLayerSettings settings;
3108   setDefaultLabelParams( settings );
3109 
3110   QgsTextFormat format = settings.format();
3111   format.setSize( 20 );
3112   format.setColor( QColor( 0, 0, 0 ) );
3113   settings.setFormat( format );
3114 
3115   settings.fieldName = QStringLiteral( "'XXXXXXXX'" );
3116   settings.isExpression = true;
3117   settings.placement = QgsPalLayerSettings::Horizontal;
3118   settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine );
3119   settings.labelPerPart = false;
3120   settings.lineSettings().setLineAnchorPercent( 0.0 );
3121 
3122   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
3123   vl2->setRenderer( new QgsNullSymbolRenderer() );
3124 
3125   QgsFeature f;
3126   f.setAttributes( QgsAttributes() << 1 );
3127   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190000 5000010, 190200 5000000)" ) ) );
3128   QVERIFY( vl2->dataProvider()->addFeature( f ) );
3129 
3130   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
3131   vl2->setLabelsEnabled( true );
3132 
3133   // make a fake render context
3134   QSize size( 640, 480 );
3135   QgsMapSettings mapSettings;
3136   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
3137   mapSettings.setDestinationCrs( vl2->crs() );
3138 
3139   mapSettings.setOutputSize( size );
3140   mapSettings.setExtent( f.geometry().boundingBox().buffered( 10 ) );
3141   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
3142   mapSettings.setOutputDpi( 96 );
3143 
3144   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
3145   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
3146   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
3147   // engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
3148   mapSettings.setLabelingEngineSettings( engineSettings );
3149 
3150   QgsMapRendererSequentialJob job( mapSettings );
3151   job.start();
3152   job.waitForFinished();
3153 
3154   QImage img = job.renderedImage();
3155   QVERIFY( imageCheck( QStringLiteral( "horizontal_anchor_start" ), img, 20 ) );
3156 
3157   settings.lineSettings().setLineAnchorPercent( 1.0 );
3158   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
3159   QgsMapRendererSequentialJob job2( mapSettings );
3160   job2.start();
3161   job2.waitForFinished();
3162 
3163   img = job2.renderedImage();
3164   QVERIFY( imageCheck( QStringLiteral( "horizontal_anchor_end" ), img, 20 ) );
3165 }
3166 
testLineAnchorHorizontalConstraints()3167 void TestQgsLabelingEngine::testLineAnchorHorizontalConstraints()
3168 {
3169   // test line label anchor with parallel labels
3170   QgsPalLayerSettings settings;
3171   setDefaultLabelParams( settings );
3172 
3173   QgsTextFormat format = settings.format();
3174   format.setSize( 20 );
3175   format.setColor( QColor( 0, 0, 0 ) );
3176   settings.setFormat( format );
3177 
3178   settings.fieldName = QStringLiteral( "l" );
3179   settings.isExpression = false;
3180   settings.placement = QgsPalLayerSettings::Horizontal;
3181   settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine );
3182   settings.labelPerPart = false;
3183   settings.lineSettings().setLineAnchorPercent( 0.0 );
3184   settings.lineSettings().setAnchorType( QgsLabelLineSettings::AnchorType::HintOnly );
3185 
3186   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3946&field=l:string" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
3187   vl2->setRenderer( new QgsNullSymbolRenderer() );
3188 
3189   QgsFeature f;
3190   f.setAttributes( QgsAttributes() << QVariant() );
3191 
3192   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190030 5000000, 190030 5000010)" ) ) );
3193   QVERIFY( vl2->dataProvider()->addFeature( f ) );
3194 
3195   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190170 5000000, 190170 5000010)" ) ) );
3196   QVERIFY( vl2->dataProvider()->addFeature( f ) );
3197 
3198   f.setAttributes( QgsAttributes() << QStringLiteral( "XXXXXXXX" ) );
3199   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (190020 5000000, 190030 5000010, 190170 5000010, 190190 5000000)" ) ) );
3200   QVERIFY( vl2->dataProvider()->addFeature( f ) );
3201 
3202   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
3203   vl2->setLabelsEnabled( true );
3204 
3205   // make a fake render context
3206   QSize size( 640, 480 );
3207   QgsMapSettings mapSettings;
3208   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
3209   mapSettings.setDestinationCrs( vl2->crs() );
3210 
3211   mapSettings.setOutputSize( size );
3212   mapSettings.setExtent( f.geometry().boundingBox().buffered( 40 ) );
3213   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
3214   mapSettings.setOutputDpi( 96 );
3215 
3216   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
3217   engineSettings.setFlag( QgsLabelingEngineSettings::UsePartialCandidates, false );
3218   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
3219   // engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
3220   mapSettings.setLabelingEngineSettings( engineSettings );
3221 
3222   QgsMapRendererSequentialJob job( mapSettings );
3223   job.start();
3224   job.waitForFinished();
3225 
3226   QImage img = job.renderedImage();
3227   QVERIFY( imageCheck( QStringLiteral( "horizontal_hint_anchor_start" ), img, 20 ) );
3228 
3229   settings.lineSettings().setLineAnchorPercent( 1.0 );
3230   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
3231   QgsMapRendererSequentialJob job2( mapSettings );
3232   job2.start();
3233   job2.waitForFinished();
3234 
3235   img = job2.renderedImage();
3236   QVERIFY( imageCheck( QStringLiteral( "horizontal_hint_anchor_end" ), img, 20 ) );
3237 
3238   settings.lineSettings().setAnchorType( QgsLabelLineSettings::AnchorType::Strict );
3239   settings.lineSettings().setLineAnchorPercent( 0.0 );
3240   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
3241   QgsMapRendererSequentialJob job3( mapSettings );
3242   job3.start();
3243   job3.waitForFinished();
3244 
3245   img = job3.renderedImage();
3246   QVERIFY( imageCheck( QStringLiteral( "horizontal_strict_anchor_start" ), img, 20 ) );
3247 
3248   settings.lineSettings().setLineAnchorPercent( 1.0 );
3249   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
3250   QgsMapRendererSequentialJob job4( mapSettings );
3251   job4.start();
3252   job4.waitForFinished();
3253 
3254   img = job4.renderedImage();
3255   QVERIFY( imageCheck( QStringLiteral( "horizontal_strict_anchor_end" ), img, 20 ) );
3256 }
3257 
testShowAllLabelsWhenALabelHasNoCandidates()3258 void TestQgsLabelingEngine::testShowAllLabelsWhenALabelHasNoCandidates()
3259 {
3260   // test that showing all labels when a label has no candidate placements doesn't
3261   // result in a crash
3262   // refs https://github.com/qgis/QGIS/issues/38093
3263 
3264   QgsPalLayerSettings settings;
3265   setDefaultLabelParams( settings );
3266 
3267   QgsTextFormat format = settings.format();
3268   format.setSize( 20 );
3269   format.setColor( QColor( 0, 0, 0 ) );
3270   settings.setFormat( format );
3271 
3272   settings.fieldName = QStringLiteral( "'xxxxxxxxxxxxxx'" );
3273   settings.isExpression = true;
3274   settings.placement = QgsPalLayerSettings::Line;
3275   settings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::OnLine );
3276   settings.obstacleSettings().setFactor( 10 );
3277   settings.lineSettings().setOverrunDistance( 50 );
3278 
3279   std::unique_ptr< QgsVectorLayer> vl2( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:23700&field=l:string" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
3280   vl2->setRenderer( new QgsNullSymbolRenderer() );
3281 
3282   QgsFeature f;
3283   f.setAttributes( QgsAttributes() << QVariant() );
3284 
3285   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (-2446233 -5204828, -2342845 -5203825)" ) ) );
3286   QVERIFY( vl2->dataProvider()->addFeature( f ) );
3287 
3288   f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineString (-2439207 -5198806, -2331302 -5197802)" ) ) );
3289   QVERIFY( vl2->dataProvider()->addFeature( f ) );
3290 
3291   vl2->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );  // TODO: this should not be necessary!
3292   vl2->setLabelsEnabled( true );
3293 
3294   // make a fake render context
3295   QSize size( 640, 480 );
3296   QgsMapSettings mapSettings;
3297   mapSettings.setLabelingEngineSettings( createLabelEngineSettings() );
3298   mapSettings.setDestinationCrs( vl2->crs() );
3299 
3300   mapSettings.setOutputSize( size );
3301   mapSettings.setExtent( QgsRectangle( -3328044.9, -5963176., -1127782.7, -4276844.3 ) );
3302   mapSettings.setLayers( QList<QgsMapLayer *>() << vl2.get() );
3303   mapSettings.setOutputDpi( 96 );
3304 
3305   QgsLabelingEngineSettings engineSettings = mapSettings.labelingEngineSettings();
3306   engineSettings.setFlag( QgsLabelingEngineSettings::DrawLabelRectOnly, true );
3307   engineSettings.setFlag( QgsLabelingEngineSettings::UseAllLabels, true );
3308   engineSettings.setFlag( QgsLabelingEngineSettings::DrawUnplacedLabels, true );
3309   // engineSettings.setFlag( QgsLabelingEngineSettings::DrawCandidates, true );
3310   mapSettings.setLabelingEngineSettings( engineSettings );
3311 
3312   QgsMapRendererSequentialJob job( mapSettings );
3313   job.start();
3314   job.waitForFinished();
3315 
3316   QImage img = job.renderedImage();
3317   QVERIFY( imageCheck( QStringLiteral( "show_all_labels_when_no_candidates" ), img, 20 ) );
3318 }
3319 
3320 QGSTEST_MAIN( TestQgsLabelingEngine )
3321 #include "testqgslabelingengine.moc"
3322