Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsvectorlayerlabelprovider.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 "qgsvectorlayerlabelprovider.h"
17 : :
18 : : #include "qgsgeometry.h"
19 : : #include "qgslabelsearchtree.h"
20 : : #include "qgspallabeling.h"
21 : : #include "qgstextlabelfeature.h"
22 : : #include "qgsvectorlayer.h"
23 : : #include "qgsvectorlayerfeatureiterator.h"
24 : : #include "qgsrenderer.h"
25 : : #include "qgspolygon.h"
26 : : #include "qgslinestring.h"
27 : : #include "qgsmultipolygon.h"
28 : : #include "qgslogger.h"
29 : : #include "qgsexpressioncontextutils.h"
30 : : #include "qgsmaskidprovider.h"
31 : : #include "qgstextcharacterformat.h"
32 : : #include "qgstextfragment.h"
33 : : #include "qgslabelingresults.h"
34 : :
35 : : #include "feature.h"
36 : : #include "labelposition.h"
37 : : #include "callouts/qgscallout.h"
38 : :
39 : : #include "pal/layer.h"
40 : :
41 : : #include <QPicture>
42 : : #include <QTextDocument>
43 : : #include <QTextFragment>
44 : :
45 : : using namespace pal;
46 : :
47 : 0 : QgsVectorLayerLabelProvider::QgsVectorLayerLabelProvider( QgsVectorLayer *layer, const QString &providerId, bool withFeatureLoop, const QgsPalLayerSettings *settings, const QString &layerName )
48 : 0 : : QgsAbstractLabelProvider( layer, providerId )
49 : 0 : , mSettings( settings ? * settings : QgsPalLayerSettings() ) // TODO: all providers should have valid settings?
50 : 0 : , mLayerGeometryType( layer->geometryType() )
51 : 0 : , mRenderer( layer->renderer() )
52 : 0 : , mFields( layer->fields() )
53 : 0 : , mCrs( layer->crs() )
54 : 0 : {
55 : 0 : mName = layerName.isEmpty() ? layer->id() : layerName;
56 : :
57 : 0 : if ( withFeatureLoop )
58 : : {
59 : 0 : mSource = std::make_unique<QgsVectorLayerFeatureSource>( layer );
60 : 0 : }
61 : :
62 : 0 : init();
63 : 0 : }
64 : :
65 : 0 : QgsVectorLayerLabelProvider::QgsVectorLayerLabelProvider( QgsWkbTypes::GeometryType geometryType, const QgsFields &fields, const QgsCoordinateReferenceSystem &crs, const QString &providerId, const QgsPalLayerSettings *settings, QgsMapLayer *layer, const QString &layerName )
66 : 0 : : QgsAbstractLabelProvider( layer, providerId )
67 : 0 : , mSettings( settings ? * settings : QgsPalLayerSettings() ) // TODO: all providers should have valid settings?
68 : 0 : , mLayerGeometryType( geometryType )
69 : 0 : , mRenderer( nullptr )
70 : 0 : , mFields( fields )
71 : 0 : , mCrs( crs )
72 : 0 : {
73 : 0 : mName = layerName.isEmpty() ? layer->id() : layerName;
74 : :
75 : 0 : init();
76 : 0 : }
77 : :
78 : 0 : void QgsVectorLayerLabelProvider::init()
79 : : {
80 : 0 : mPlacement = mSettings.placement;
81 : 0 : mFlags = Flags();
82 : 0 : if ( mSettings.drawLabels )
83 : 0 : mFlags |= DrawLabels;
84 : 0 : if ( mSettings.displayAll )
85 : 0 : mFlags |= DrawAllLabels;
86 : 0 : if ( mSettings.lineSettings().mergeLines() && !mSettings.lineSettings().addDirectionSymbol() )
87 : 0 : mFlags |= MergeConnectedLines;
88 : 0 : if ( mSettings.centroidInside )
89 : 0 : mFlags |= CentroidMustBeInside;
90 : :
91 : 0 : mPriority = 1 - mSettings.priority / 10.0; // convert 0..10 --> 1..0
92 : :
93 : 0 : if ( mLayerGeometryType == QgsWkbTypes::PointGeometry && mRenderer )
94 : : {
95 : : //override obstacle type to treat any intersection of a label with the point symbol as a high cost conflict
96 : 0 : mObstacleType = QgsLabelObstacleSettings::PolygonWhole;
97 : 0 : }
98 : : else
99 : : {
100 : 0 : mObstacleType = mSettings.obstacleSettings().type();
101 : : }
102 : :
103 : 0 : mUpsidedownLabels = mSettings.upsidedownLabels;
104 : 0 : }
105 : :
106 : :
107 : 0 : QgsVectorLayerLabelProvider::~QgsVectorLayerLabelProvider()
108 : 0 : {
109 : 0 : qDeleteAll( mLabels );
110 : 0 : }
111 : :
112 : :
113 : 0 : bool QgsVectorLayerLabelProvider::prepare( QgsRenderContext &context, QSet<QString> &attributeNames )
114 : : {
115 : 0 : const QgsMapSettings &mapSettings = mEngine->mapSettings();
116 : :
117 : 0 : return mSettings.prepare( context, attributeNames, mFields, mapSettings, mCrs );
118 : : }
119 : :
120 : 0 : void QgsVectorLayerLabelProvider::startRender( QgsRenderContext &context )
121 : : {
122 : 0 : QgsAbstractLabelProvider::startRender( context );
123 : 0 : mSettings.startRender( context );
124 : 0 : }
125 : :
126 : 0 : void QgsVectorLayerLabelProvider::stopRender( QgsRenderContext &context )
127 : : {
128 : 0 : QgsAbstractLabelProvider::stopRender( context );
129 : 0 : mSettings.stopRender( context );
130 : 0 : }
131 : :
132 : 0 : QList<QgsLabelFeature *> QgsVectorLayerLabelProvider::labelFeatures( QgsRenderContext &ctx )
133 : : {
134 : 0 : if ( !mSource )
135 : : {
136 : : // we have created the provider with "own feature loop" == false
137 : : // so it is assumed that prepare() has been already called followed by registerFeature() calls
138 : 0 : return mLabels;
139 : : }
140 : :
141 : 0 : QSet<QString> attrNames;
142 : 0 : if ( !prepare( ctx, attrNames ) )
143 : 0 : return QList<QgsLabelFeature *>();
144 : :
145 : 0 : if ( mRenderer )
146 : 0 : mRenderer->startRender( ctx, mFields );
147 : :
148 : 0 : QgsRectangle layerExtent = ctx.extent();
149 : 0 : if ( mSettings.ct.isValid() && !mSettings.ct.isShortCircuited() )
150 : 0 : layerExtent = mSettings.ct.transformBoundingBox( ctx.extent(), QgsCoordinateTransform::ReverseTransform );
151 : :
152 : 0 : QgsFeatureRequest request;
153 : 0 : request.setFilterRect( layerExtent );
154 : 0 : request.setSubsetOfAttributes( attrNames, mFields );
155 : 0 : QgsFeatureIterator fit = mSource->getFeatures( request );
156 : :
157 : 0 : QgsExpressionContextScope *symbolScope = new QgsExpressionContextScope();
158 : 0 : ctx.expressionContext().appendScope( symbolScope );
159 : 0 : QgsFeature fet;
160 : 0 : while ( fit.nextFeature( fet ) )
161 : : {
162 : 0 : QgsGeometry obstacleGeometry;
163 : 0 : const QgsSymbol *symbol = nullptr;
164 : 0 : if ( mRenderer )
165 : : {
166 : 0 : QgsSymbolList symbols = mRenderer->originalSymbolsForFeature( fet, ctx );
167 : 0 : if ( !symbols.isEmpty() && fet.geometry().type() == QgsWkbTypes::PointGeometry )
168 : : {
169 : : //point feature, use symbol bounds as obstacle
170 : 0 : obstacleGeometry = QgsVectorLayerLabelProvider::getPointObstacleGeometry( fet, ctx, symbols );
171 : 0 : }
172 : 0 : if ( !symbols.isEmpty() )
173 : : {
174 : 0 : symbol = symbols.at( 0 );
175 : 0 : symbolScope = QgsExpressionContextUtils::updateSymbolScope( symbol, symbolScope );
176 : 0 : }
177 : 0 : }
178 : 0 : ctx.expressionContext().setFeature( fet );
179 : 0 : registerFeature( fet, ctx, obstacleGeometry, symbol );
180 : 0 : }
181 : :
182 : 0 : if ( ctx.expressionContext().lastScope() == symbolScope )
183 : 0 : delete ctx.expressionContext().popScope();
184 : :
185 : 0 : if ( mRenderer )
186 : 0 : mRenderer->stopRender( ctx );
187 : :
188 : 0 : return mLabels;
189 : 0 : }
190 : :
191 : 0 : void QgsVectorLayerLabelProvider::registerFeature( const QgsFeature &feature, QgsRenderContext &context, const QgsGeometry &obstacleGeometry, const QgsSymbol *symbol )
192 : : {
193 : 0 : QgsLabelFeature *label = nullptr;
194 : :
195 : 0 : mSettings.registerFeature( feature, context, &label, obstacleGeometry, symbol );
196 : 0 : if ( label )
197 : 0 : mLabels << label;
198 : 0 : }
199 : :
200 : 0 : QgsGeometry QgsVectorLayerLabelProvider::getPointObstacleGeometry( QgsFeature &fet, QgsRenderContext &context, const QgsSymbolList &symbols )
201 : : {
202 : 0 : if ( !fet.hasGeometry() || fet.geometry().type() != QgsWkbTypes::PointGeometry )
203 : 0 : return QgsGeometry();
204 : :
205 : 0 : bool isMultiPoint = fet.geometry().constGet()->nCoordinates() > 1;
206 : 0 : std::unique_ptr< QgsAbstractGeometry > obstacleGeom;
207 : 0 : if ( isMultiPoint )
208 : 0 : obstacleGeom = std::make_unique< QgsMultiPolygon >();
209 : :
210 : : // for each point
211 : 0 : for ( int i = 0; i < fet.geometry().constGet()->nCoordinates(); ++i )
212 : : {
213 : 0 : QRectF bounds;
214 : 0 : QgsPoint p = fet.geometry().constGet()->vertexAt( QgsVertexId( i, 0, 0 ) );
215 : 0 : double x = p.x();
216 : 0 : double y = p.y();
217 : 0 : double z = 0; // dummy variable for coordinate transforms
218 : :
219 : : //transform point to pixels
220 : 0 : if ( context.coordinateTransform().isValid() )
221 : : {
222 : : try
223 : : {
224 : 0 : context.coordinateTransform().transformInPlace( x, y, z );
225 : 0 : }
226 : : catch ( QgsCsException & )
227 : : {
228 : 0 : return QgsGeometry();
229 : 0 : }
230 : 0 : }
231 : 0 : context.mapToPixel().transformInPlace( x, y );
232 : :
233 : 0 : QPointF pt( x, y );
234 : 0 : const auto constSymbols = symbols;
235 : 0 : for ( QgsSymbol *symbol : constSymbols )
236 : : {
237 : 0 : if ( symbol->type() == QgsSymbol::Marker )
238 : : {
239 : 0 : if ( bounds.isValid() )
240 : 0 : bounds = bounds.united( static_cast< QgsMarkerSymbol * >( symbol )->bounds( pt, context, fet ) );
241 : : else
242 : 0 : bounds = static_cast< QgsMarkerSymbol * >( symbol )->bounds( pt, context, fet );
243 : 0 : }
244 : : }
245 : :
246 : : //convert bounds to a geometry
247 : 0 : QVector< double > bX;
248 : 0 : bX << bounds.left() << bounds.right() << bounds.right() << bounds.left();
249 : 0 : QVector< double > bY;
250 : 0 : bY << bounds.top() << bounds.top() << bounds.bottom() << bounds.bottom();
251 : 0 : std::unique_ptr< QgsLineString > boundLineString = std::make_unique< QgsLineString >( bX, bY );
252 : :
253 : : //then transform back to map units
254 : : //TODO - remove when labeling is refactored to use screen units
255 : 0 : for ( int i = 0; i < boundLineString->numPoints(); ++i )
256 : : {
257 : 0 : QgsPointXY point = context.mapToPixel().toMapCoordinates( static_cast<int>( boundLineString->xAt( i ) ),
258 : 0 : static_cast<int>( boundLineString->yAt( i ) ) );
259 : 0 : boundLineString->setXAt( i, point.x() );
260 : 0 : boundLineString->setYAt( i, point.y() );
261 : 0 : }
262 : 0 : if ( context.coordinateTransform().isValid() )
263 : : {
264 : : try
265 : : {
266 : 0 : boundLineString->transform( context.coordinateTransform(), QgsCoordinateTransform::ReverseTransform );
267 : 0 : }
268 : : catch ( QgsCsException & )
269 : : {
270 : 0 : return QgsGeometry();
271 : 0 : }
272 : 0 : }
273 : 0 : boundLineString->close();
274 : :
275 : 0 : if ( context.coordinateTransform().isValid() )
276 : : {
277 : : // coordinate transforms may have resulted in nan coordinates - if so, strip these out
278 : 0 : boundLineString->filterVertices( []( const QgsPoint & point )->bool
279 : : {
280 : 0 : return std::isfinite( point.x() ) && std::isfinite( point.y() );
281 : : } );
282 : 0 : if ( !boundLineString->isRing() )
283 : 0 : return QgsGeometry();
284 : 0 : }
285 : :
286 : 0 : std::unique_ptr< QgsPolygon > obstaclePolygon = std::make_unique< QgsPolygon >();
287 : 0 : obstaclePolygon->setExteriorRing( boundLineString.release() );
288 : :
289 : 0 : if ( isMultiPoint )
290 : : {
291 : 0 : static_cast<QgsMultiPolygon *>( obstacleGeom.get() )->addGeometry( obstaclePolygon.release() );
292 : 0 : }
293 : : else
294 : : {
295 : 0 : obstacleGeom = std::move( obstaclePolygon );
296 : : }
297 : 0 : }
298 : :
299 : 0 : return QgsGeometry( std::move( obstacleGeom ) );
300 : 0 : }
301 : :
302 : 0 : void QgsVectorLayerLabelProvider::drawLabelBackground( QgsRenderContext &context, LabelPosition *label ) const
303 : : {
304 : 0 : if ( !mSettings.drawLabels )
305 : 0 : return;
306 : :
307 : : // render callout
308 : 0 : if ( mSettings.callout() && mSettings.callout()->drawOrder() == QgsCallout::OrderBelowAllLabels )
309 : : {
310 : 0 : drawCallout( context, label );
311 : 0 : }
312 : 0 : }
313 : :
314 : 0 : void QgsVectorLayerLabelProvider::drawCallout( QgsRenderContext &context, pal::LabelPosition *label ) const
315 : : {
316 : 0 : bool enabled = mSettings.callout()->enabled();
317 : 0 : if ( mSettings.dataDefinedProperties().isActive( QgsPalLayerSettings::CalloutDraw ) )
318 : : {
319 : 0 : context.expressionContext().setOriginalValueVariable( enabled );
320 : 0 : enabled = mSettings.dataDefinedProperties().valueAsBool( QgsPalLayerSettings::CalloutDraw, context.expressionContext(), enabled );
321 : 0 : }
322 : 0 : if ( enabled )
323 : : {
324 : 0 : QgsMapToPixel xform = context.mapToPixel();
325 : 0 : xform.setMapRotation( 0, 0, 0 );
326 : 0 : QPointF outPt = xform.transform( label->getX(), label->getY() ).toQPointF();
327 : 0 : QgsPointXY outPt2 = xform.transform( label->getX() + label->getWidth(), label->getY() + label->getHeight() );
328 : 0 : QRectF rect( outPt.x(), outPt.y(), outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
329 : :
330 : 0 : QgsGeometry g( QgsGeos::fromGeos( label->getFeaturePart()->feature()->geometry() ) );
331 : 0 : g.transform( xform.transform() );
332 : 0 : QgsCallout::QgsCalloutContext calloutContext;
333 : 0 : calloutContext.allFeaturePartsLabeled = label->getFeaturePart()->feature()->labelAllParts();
334 : 0 : calloutContext.originalFeatureCrs = label->getFeaturePart()->feature()->originalFeatureCrs();
335 : 0 : mSettings.callout()->render( context, rect, label->getAlpha() * 180 / M_PI, g, calloutContext );
336 : :
337 : 0 : const QList< QgsCalloutPosition > renderedPositions = calloutContext.positions();
338 : :
339 : 0 : for ( QgsCalloutPosition position : renderedPositions )
340 : : {
341 : 0 : position.layerID = mLayerId;
342 : 0 : position.featureId = label->getFeaturePart()->featureId();
343 : 0 : position.providerID = mProviderId;
344 : 0 : mEngine->results()->mLabelSearchTree->insertCallout( position );
345 : 0 : }
346 : 0 : }
347 : 0 : }
348 : :
349 : 0 : void QgsVectorLayerLabelProvider::drawLabel( QgsRenderContext &context, pal::LabelPosition *label ) const
350 : : {
351 : 0 : if ( !mSettings.drawLabels )
352 : 0 : return;
353 : :
354 : 0 : QgsTextLabelFeature *lf = dynamic_cast<QgsTextLabelFeature *>( label->getFeaturePart()->feature() );
355 : :
356 : : // Copy to temp, editable layer settings
357 : : // these settings will be changed by any data defined values, then used for rendering label components
358 : : // settings may be adjusted during rendering of components
359 : 0 : QgsPalLayerSettings tmpLyr( mSettings );
360 : :
361 : : // apply any previously applied data defined settings for the label
362 : 0 : const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues = lf->dataDefinedValues();
363 : :
364 : : //font
365 : 0 : QFont dFont = lf->definedFont();
366 : 0 : QgsDebugMsgLevel( QStringLiteral( "PAL font tmpLyr: %1, Style: %2" ).arg( tmpLyr.format().font().toString(), tmpLyr.format().font().styleName() ), 4 );
367 : 0 : QgsDebugMsgLevel( QStringLiteral( "PAL font definedFont: %1, Style: %2" ).arg( dFont.toString(), dFont.styleName() ), 4 );
368 : :
369 : 0 : QgsTextFormat format = tmpLyr.format();
370 : 0 : format.setFont( dFont );
371 : :
372 : : // size has already been calculated and stored in the defined font - this calculated size
373 : : // is in pixels
374 : 0 : format.setSize( dFont.pixelSize() );
375 : 0 : format.setSizeUnit( QgsUnitTypes::RenderPixels );
376 : 0 : tmpLyr.setFormat( format );
377 : :
378 : 0 : if ( tmpLyr.multilineAlign == QgsPalLayerSettings::MultiFollowPlacement )
379 : : {
380 : : //calculate font alignment based on label quadrant
381 : 0 : switch ( label->getQuadrant() )
382 : : {
383 : : case LabelPosition::QuadrantAboveLeft:
384 : : case LabelPosition::QuadrantLeft:
385 : : case LabelPosition::QuadrantBelowLeft:
386 : 0 : tmpLyr.multilineAlign = QgsPalLayerSettings::MultiRight;
387 : 0 : break;
388 : : case LabelPosition::QuadrantAbove:
389 : : case LabelPosition::QuadrantOver:
390 : : case LabelPosition::QuadrantBelow:
391 : 0 : tmpLyr.multilineAlign = QgsPalLayerSettings::MultiCenter;
392 : 0 : break;
393 : : case LabelPosition::QuadrantAboveRight:
394 : : case LabelPosition::QuadrantRight:
395 : : case LabelPosition::QuadrantBelowRight:
396 : 0 : tmpLyr.multilineAlign = QgsPalLayerSettings::MultiLeft;
397 : 0 : break;
398 : : }
399 : 0 : }
400 : :
401 : : // update tmpLyr with any data defined text style values
402 : 0 : QgsPalLabeling::dataDefinedTextStyle( tmpLyr, ddValues );
403 : :
404 : : // update tmpLyr with any data defined text buffer values
405 : 0 : QgsPalLabeling::dataDefinedTextBuffer( tmpLyr, ddValues );
406 : :
407 : : // update tmpLyr with any data defined text mask values
408 : 0 : QgsPalLabeling::dataDefinedTextMask( tmpLyr, ddValues );
409 : :
410 : : // update tmpLyr with any data defined text formatting values
411 : 0 : QgsPalLabeling::dataDefinedTextFormatting( tmpLyr, ddValues );
412 : :
413 : : // update tmpLyr with any data defined shape background values
414 : 0 : QgsPalLabeling::dataDefinedShapeBackground( tmpLyr, ddValues );
415 : :
416 : : // update tmpLyr with any data defined drop shadow values
417 : 0 : QgsPalLabeling::dataDefinedDropShadow( tmpLyr, ddValues );
418 : :
419 : : // Render the components of a label in reverse order
420 : : // (backgrounds -> text)
421 : :
422 : : // render callout
423 : 0 : if ( mSettings.callout() && mSettings.callout()->drawOrder() == QgsCallout::OrderBelowIndividualLabels )
424 : : {
425 : 0 : drawCallout( context, label );
426 : 0 : }
427 : :
428 : 0 : if ( tmpLyr.format().shadow().enabled() && tmpLyr.format().shadow().shadowPlacement() == QgsTextShadowSettings::ShadowLowest )
429 : : {
430 : 0 : QgsTextFormat format = tmpLyr.format();
431 : :
432 : 0 : if ( tmpLyr.format().background().enabled() && tmpLyr.format().background().type() != QgsTextBackgroundSettings::ShapeMarkerSymbol ) // background shadows not compatible with marker symbol backgrounds
433 : : {
434 : 0 : format.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowShape );
435 : 0 : }
436 : 0 : else if ( tmpLyr.format().buffer().enabled() )
437 : : {
438 : 0 : format.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowBuffer );
439 : 0 : }
440 : : else
441 : : {
442 : 0 : format.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowText );
443 : : }
444 : :
445 : 0 : tmpLyr.setFormat( format );
446 : 0 : }
447 : :
448 : 0 : if ( tmpLyr.format().background().enabled() )
449 : : {
450 : 0 : drawLabelPrivate( label, context, tmpLyr, QgsTextRenderer::Background );
451 : 0 : }
452 : :
453 : 0 : if ( tmpLyr.format().buffer().enabled() )
454 : : {
455 : 0 : drawLabelPrivate( label, context, tmpLyr, QgsTextRenderer::Buffer );
456 : 0 : }
457 : :
458 : 0 : drawLabelPrivate( label, context, tmpLyr, QgsTextRenderer::Text );
459 : :
460 : : // add to the results
461 : 0 : QString labeltext = label->getFeaturePart()->feature()->labelText();
462 : 0 : mEngine->results()->mLabelSearchTree->insertLabel( label, label->getFeaturePart()->featureId(), mLayerId, labeltext, dFont, false, lf->hasFixedPosition(), mProviderId );
463 : 0 : }
464 : :
465 : 0 : void QgsVectorLayerLabelProvider::drawUnplacedLabel( QgsRenderContext &context, LabelPosition *label ) const
466 : : {
467 : 0 : if ( !mSettings.drawLabels )
468 : 0 : return;
469 : :
470 : 0 : QgsTextLabelFeature *lf = dynamic_cast<QgsTextLabelFeature *>( label->getFeaturePart()->feature() );
471 : :
472 : 0 : QgsPalLayerSettings tmpLyr( mSettings );
473 : 0 : QgsTextFormat format = tmpLyr.format();
474 : 0 : format.setColor( mEngine->engineSettings().unplacedLabelColor() );
475 : 0 : tmpLyr.setFormat( format );
476 : 0 : drawLabelPrivate( label, context, tmpLyr, QgsTextRenderer::Text );
477 : :
478 : : // add to the results
479 : 0 : QString labeltext = label->getFeaturePart()->feature()->labelText();
480 : 0 : mEngine->results()->mLabelSearchTree->insertLabel( label, label->getFeaturePart()->featureId(), mLayerId, labeltext, tmpLyr.format().font(), false, lf->hasFixedPosition(), mProviderId, true );
481 : 0 : }
482 : :
483 : 0 : void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, QgsRenderContext &context, QgsPalLayerSettings &tmpLyr, QgsTextRenderer::TextPart drawType, double dpiRatio ) const
484 : : {
485 : : // NOTE: this is repeatedly called for multi-part labels
486 : 0 : QPainter *painter = context.painter();
487 : :
488 : : // features are pre-rotated but not scaled/translated,
489 : : // so we only disable rotation here. Ideally, they'd be
490 : : // also pre-scaled/translated, as suggested here:
491 : : // https://github.com/qgis/QGIS/issues/20071
492 : 0 : QgsMapToPixel xform = context.mapToPixel();
493 : 0 : xform.setMapRotation( 0, 0, 0 );
494 : :
495 : 0 : QPointF outPt = xform.transform( label->getX(), label->getY() ).toQPointF();
496 : :
497 : 0 : if ( mEngine->engineSettings().testFlag( QgsLabelingEngineSettings::DrawLabelRectOnly ) ) // TODO: this should get directly to labeling engine
498 : : {
499 : : //debugging rect
500 : 0 : if ( drawType != QgsTextRenderer::Text )
501 : 0 : return;
502 : :
503 : 0 : QgsPointXY outPt2 = xform.transform( label->getX() + label->getWidth(), label->getY() + label->getHeight() );
504 : 0 : QRectF rect( 0, 0, outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
505 : 0 : painter->save();
506 : 0 : painter->setRenderHint( QPainter::Antialiasing, false );
507 : 0 : painter->translate( QPointF( outPt.x(), outPt.y() ) );
508 : 0 : painter->rotate( -label->getAlpha() * 180 / M_PI );
509 : :
510 : 0 : if ( label->conflictsWithObstacle() )
511 : : {
512 : 0 : painter->setBrush( QColor( 255, 0, 0, 100 ) );
513 : 0 : painter->setPen( QColor( 255, 0, 0, 150 ) );
514 : 0 : }
515 : : else
516 : : {
517 : 0 : painter->setBrush( QColor( 0, 255, 0, 100 ) );
518 : 0 : painter->setPen( QColor( 0, 255, 0, 150 ) );
519 : : }
520 : :
521 : 0 : painter->drawRect( rect );
522 : 0 : painter->restore();
523 : :
524 : 0 : if ( label->nextPart() )
525 : 0 : drawLabelPrivate( label->nextPart(), context, tmpLyr, drawType, dpiRatio );
526 : :
527 : 0 : return;
528 : : }
529 : :
530 : 0 : QgsTextRenderer::Component component;
531 : 0 : component.dpiRatio = dpiRatio;
532 : 0 : component.origin = outPt;
533 : 0 : component.rotation = label->getAlpha();
534 : :
535 : 0 : if ( drawType == QgsTextRenderer::Background )
536 : : {
537 : : // get rotated label's center point
538 : 0 : QPointF centerPt( outPt );
539 : 0 : QgsPointXY outPt2 = xform.transform( label->getX() + label->getWidth() / 2,
540 : 0 : label->getY() + label->getHeight() / 2 );
541 : :
542 : 0 : double xc = outPt2.x() - outPt.x();
543 : 0 : double yc = outPt2.y() - outPt.y();
544 : :
545 : 0 : double angle = -component.rotation;
546 : 0 : double xd = xc * std::cos( angle ) - yc * std::sin( angle );
547 : 0 : double yd = xc * std::sin( angle ) + yc * std::cos( angle );
548 : :
549 : 0 : centerPt.setX( centerPt.x() + xd );
550 : 0 : centerPt.setY( centerPt.y() + yd );
551 : :
552 : 0 : component.center = centerPt;
553 : :
554 : : // convert label size to render units
555 : 0 : double labelWidthPx = context.convertToPainterUnits( label->getWidth(), QgsUnitTypes::RenderMapUnits, QgsMapUnitScale() );
556 : 0 : double labelHeightPx = context.convertToPainterUnits( label->getHeight(), QgsUnitTypes::RenderMapUnits, QgsMapUnitScale() );
557 : :
558 : 0 : component.size = QSizeF( labelWidthPx, labelHeightPx );
559 : :
560 : 0 : QgsTextRenderer::drawBackground( context, component, tmpLyr.format(), QgsTextDocument(), QgsTextRenderer::Label );
561 : 0 : }
562 : :
563 : 0 : else if ( drawType == QgsTextRenderer::Buffer
564 : 0 : || drawType == QgsTextRenderer::Text )
565 : : {
566 : :
567 : : // TODO: optimize access :)
568 : 0 : QgsTextLabelFeature *lf = static_cast<QgsTextLabelFeature *>( label->getFeaturePart()->feature() );
569 : 0 : QString txt = lf->text( label->getPartId() );
570 : 0 : QFontMetricsF *labelfm = lf->labelFontMetrics();
571 : :
572 : 0 : if ( auto *lMaskIdProvider = context.maskIdProvider() )
573 : : {
574 : 0 : int maskId = lMaskIdProvider->maskId( label->getFeaturePart()->layer()->provider()->layerId(),
575 : 0 : label->getFeaturePart()->layer()->provider()->providerId() );
576 : 0 : context.setCurrentMaskId( maskId );
577 : 0 : }
578 : :
579 : : //add the direction symbol if needed
580 : 0 : if ( !txt.isEmpty() && tmpLyr.placement == QgsPalLayerSettings::Line &&
581 : 0 : tmpLyr.lineSettings().addDirectionSymbol() )
582 : : {
583 : 0 : bool prependSymb = false;
584 : 0 : QString symb = tmpLyr.lineSettings().rightDirectionSymbol();
585 : :
586 : 0 : if ( label->getReversed() )
587 : : {
588 : 0 : prependSymb = true;
589 : 0 : symb = tmpLyr.lineSettings().leftDirectionSymbol();
590 : 0 : }
591 : :
592 : 0 : if ( tmpLyr.lineSettings().reverseDirectionSymbol() )
593 : : {
594 : 0 : if ( symb == tmpLyr.lineSettings().rightDirectionSymbol() )
595 : : {
596 : 0 : prependSymb = true;
597 : 0 : symb = tmpLyr.lineSettings().leftDirectionSymbol();
598 : 0 : }
599 : : else
600 : : {
601 : 0 : prependSymb = false;
602 : 0 : symb = tmpLyr.lineSettings().rightDirectionSymbol();
603 : : }
604 : 0 : }
605 : :
606 : 0 : switch ( tmpLyr.lineSettings().directionSymbolPlacement() )
607 : : {
608 : : case QgsLabelLineSettings::DirectionSymbolPlacement::SymbolAbove:
609 : 0 : prependSymb = true;
610 : 0 : symb = symb + QStringLiteral( "\n" );
611 : 0 : break;
612 : :
613 : : case QgsLabelLineSettings::DirectionSymbolPlacement::SymbolBelow:
614 : 0 : prependSymb = false;
615 : 0 : symb = QStringLiteral( "\n" ) + symb;
616 : 0 : break;
617 : :
618 : : case QgsLabelLineSettings::DirectionSymbolPlacement::SymbolLeftRight:
619 : 0 : break;
620 : : }
621 : :
622 : 0 : if ( prependSymb )
623 : : {
624 : 0 : txt.prepend( symb );
625 : 0 : }
626 : : else
627 : : {
628 : 0 : txt.append( symb );
629 : : }
630 : 0 : }
631 : :
632 : 0 : QgsTextRenderer::HAlignment hAlign = QgsTextRenderer::AlignLeft;
633 : 0 : if ( tmpLyr.multilineAlign == QgsPalLayerSettings::MultiCenter )
634 : 0 : hAlign = QgsTextRenderer::AlignCenter;
635 : 0 : else if ( tmpLyr.multilineAlign == QgsPalLayerSettings::MultiRight )
636 : 0 : hAlign = QgsTextRenderer::AlignRight;
637 : 0 : else if ( tmpLyr.multilineAlign == QgsPalLayerSettings::MultiJustify )
638 : 0 : hAlign = QgsTextRenderer::AlignJustify;
639 : :
640 : 0 : QgsTextRenderer::Component component;
641 : 0 : component.origin = outPt;
642 : 0 : component.rotation = label->getAlpha();
643 : :
644 : 0 : QgsTextDocument document;
645 : 0 : if ( !tmpLyr.format().allowHtmlFormatting() || tmpLyr.placement == QgsPalLayerSettings::Curved )
646 : : {
647 : 0 : const QgsTextCharacterFormat c = lf->characterFormat( label->getPartId() );
648 : 0 : const QStringList multiLineList = QgsPalLabeling::splitToLines( txt, tmpLyr.wrapChar, tmpLyr.autoWrapLength, tmpLyr.useMaxLineLengthForAutoWrap );
649 : 0 : for ( const QString &line : multiLineList )
650 : 0 : document.append( QgsTextBlock( QgsTextFragment( line, c ) ) );
651 : 0 : }
652 : : else
653 : : {
654 : 0 : document = lf->document();
655 : : }
656 : :
657 : 0 : QgsTextRenderer::drawTextInternal( drawType, context, tmpLyr.format(), component, document, labelfm,
658 : 0 : hAlign, QgsTextRenderer::AlignTop, QgsTextRenderer::Label );
659 : :
660 : 0 : }
661 : 0 : if ( label->nextPart() )
662 : 0 : drawLabelPrivate( label->nextPart(), context, tmpLyr, drawType, dpiRatio );
663 : 0 : }
664 : :
665 : 0 : const QgsPalLayerSettings &QgsVectorLayerLabelProvider::settings() const
666 : : {
667 : 0 : return mSettings;
668 : : }
|