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