1 /***************************************************************************
2  qgssymbol.cpp
3  ---------------------
4  begin                : November 2009
5  copyright            : (C) 2009 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 <QColor>
17 #include <QImage>
18 #include <QPainter>
19 #include <QSize>
20 #include <QSvgGenerator>
21 
22 #include <cmath>
23 #include <map>
24 #include <random>
25 
26 #include "qgssymbol.h"
27 #include "qgssymbollayer.h"
28 
29 #include "qgslinesymbollayer.h"
30 #include "qgsmarkersymbollayer.h"
31 #include "qgsfillsymbollayer.h"
32 #include "qgsgeometrygeneratorsymbollayer.h"
33 #include "qgsmaptopixelgeometrysimplifier.h"
34 #include "qgslogger.h"
35 #include "qgsrendercontext.h" // for bigSymbolPreview
36 #include "qgsproject.h"
37 #include "qgsstyle.h"
38 #include "qgspainteffect.h"
39 #include "qgseffectstack.h"
40 #include "qgsvectorlayer.h"
41 #include "qgsfeature.h"
42 #include "qgsgeometry.h"
43 #include "qgsmultipoint.h"
44 #include "qgsgeometrycollection.h"
45 #include "qgslinestring.h"
46 #include "qgspolygon.h"
47 #include "qgsclipper.h"
48 #include "qgsproperty.h"
49 #include "qgscolorschemeregistry.h"
50 #include "qgsapplication.h"
51 #include "qgsexpressioncontextutils.h"
52 #include "qgsrenderedfeaturehandlerinterface.h"
53 #include "qgslegendpatchshape.h"
54 #include "qgsgeos.h"
55 #include "qgsmarkersymbol.h"
56 #include "qgslinesymbol.h"
57 #include "qgsfillsymbol.h"
58 
59 QgsPropertiesDefinition QgsSymbol::sPropertyDefinitions;
60 
61 Q_NOWARN_DEPRECATED_PUSH // because of deprecated mLayer
QgsSymbol(Qgis::SymbolType type,const QgsSymbolLayerList & layers)62 QgsSymbol::QgsSymbol( Qgis::SymbolType type, const QgsSymbolLayerList &layers )
63   : mType( type )
64   , mLayers( layers )
65 {
66 
67   // check they're all correct symbol layers
68   for ( int i = 0; i < mLayers.count(); i++ )
69   {
70     if ( !mLayers.at( i ) )
71     {
72       mLayers.removeAt( i-- );
73     }
74     else if ( !mLayers.at( i )->isCompatibleWithSymbol( this ) )
75     {
76       delete mLayers.at( i );
77       mLayers.removeAt( i-- );
78     }
79   }
80 }
81 Q_NOWARN_DEPRECATED_POP
82 
_getLineString(QgsRenderContext & context,const QgsCurve & curve,bool clipToExtent)83 QPolygonF QgsSymbol::_getLineString( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent )
84 {
85   const unsigned int nPoints = curve.numPoints();
86 
87   QgsCoordinateTransform ct = context.coordinateTransform();
88   const QgsMapToPixel &mtp = context.mapToPixel();
89   QPolygonF pts;
90 
91   //apply clipping for large lines to achieve a better rendering performance
92   if ( clipToExtent && nPoints > 1 && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) )
93   {
94     const QgsRectangle e = context.extent();
95     const double cw = e.width() / 10;
96     const double ch = e.height() / 10;
97     const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
98     pts = QgsClipper::clippedLine( curve, clipRect );
99   }
100   else
101   {
102     pts = curve.asQPolygonF();
103   }
104 
105   //transform the QPolygonF to screen coordinates
106   if ( ct.isValid() )
107   {
108     try
109     {
110       ct.transformPolygon( pts );
111     }
112     catch ( QgsCsException & )
113     {
114       // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
115     }
116   }
117 
118   // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
119   pts.erase( std::remove_if( pts.begin(), pts.end(),
120                              []( const QPointF point )
121   {
122     return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
123   } ), pts.end() );
124 
125   if ( clipToExtent && nPoints > 1 && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection )
126   {
127     // early clipping was not possible, so we have to apply it here after transformation
128     const QgsRectangle e = context.mapExtent();
129     const double cw = e.width() / 10;
130     const double ch = e.height() / 10;
131     const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
132     pts = QgsClipper::clippedLine( pts, clipRect );
133   }
134 
135   QPointF *ptr = pts.data();
136   for ( int i = 0; i < pts.size(); ++i, ++ptr )
137   {
138     mtp.transformInPlace( ptr->rx(), ptr->ry() );
139   }
140 
141   return pts;
142 }
143 
_getPolygonRing(QgsRenderContext & context,const QgsCurve & curve,const bool clipToExtent,const bool isExteriorRing,const bool correctRingOrientation)144 QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve &curve, const bool clipToExtent, const bool isExteriorRing, const bool correctRingOrientation )
145 {
146   const QgsCoordinateTransform ct = context.coordinateTransform();
147   const QgsMapToPixel &mtp = context.mapToPixel();
148 
149   QPolygonF poly = curve.asQPolygonF();
150 
151   if ( curve.numPoints() < 1 )
152     return QPolygonF();
153 
154   if ( correctRingOrientation )
155   {
156     // ensure consistent polygon ring orientation
157     if ( isExteriorRing && curve.orientation() != QgsCurve::Clockwise )
158       std::reverse( poly.begin(), poly.end() );
159     else if ( !isExteriorRing && curve.orientation() != QgsCurve::CounterClockwise )
160       std::reverse( poly.begin(), poly.end() );
161   }
162 
163   //clip close to view extent, if needed
164   if ( clipToExtent && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) && !context.extent().contains( poly.boundingRect() ) )
165   {
166     const QgsRectangle e = context.extent();
167     const double cw = e.width() / 10;
168     const double ch = e.height() / 10;
169     const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
170     QgsClipper::trimPolygon( poly, clipRect );
171   }
172 
173   //transform the QPolygonF to screen coordinates
174   if ( ct.isValid() )
175   {
176     try
177     {
178       ct.transformPolygon( poly );
179     }
180     catch ( QgsCsException & )
181     {
182       // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
183     }
184   }
185 
186   // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
187   poly.erase( std::remove_if( poly.begin(), poly.end(),
188                               []( const QPointF point )
189   {
190     return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
191   } ), poly.end() );
192 
193   if ( clipToExtent && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection && !context.mapExtent().contains( poly.boundingRect() ) )
194   {
195     // early clipping was not possible, so we have to apply it here after transformation
196     const QgsRectangle e = context.mapExtent();
197     const double cw = e.width() / 10;
198     const double ch = e.height() / 10;
199     const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
200     QgsClipper::trimPolygon( poly, clipRect );
201   }
202 
203   QPointF *ptr = poly.data();
204   for ( int i = 0; i < poly.size(); ++i, ++ptr )
205   {
206     mtp.transformInPlace( ptr->rx(), ptr->ry() );
207   }
208 
209   if ( !poly.empty() && !poly.isClosed() )
210     poly << poly.at( 0 );
211 
212   return poly;
213 }
214 
_getPolygon(QPolygonF & pts,QVector<QPolygonF> & holes,QgsRenderContext & context,const QgsPolygon & polygon,const bool clipToExtent,const bool correctRingOrientation)215 void QgsSymbol::_getPolygon( QPolygonF &pts, QVector<QPolygonF> &holes, QgsRenderContext &context, const QgsPolygon &polygon, const bool clipToExtent, const bool correctRingOrientation )
216 {
217   holes.clear();
218 
219   pts = _getPolygonRing( context, *polygon.exteriorRing(), clipToExtent, true, correctRingOrientation );
220   const int ringCount = polygon.numInteriorRings();
221   holes.reserve( ringCount );
222   for ( int idx = 0; idx < ringCount; idx++ )
223   {
224     const QPolygonF hole = _getPolygonRing( context, *( polygon.interiorRing( idx ) ), clipToExtent, false, correctRingOrientation );
225     if ( !hole.isEmpty() )
226       holes.append( hole );
227   }
228 }
229 
symbolTypeToString(Qgis::SymbolType type)230 QString QgsSymbol::symbolTypeToString( Qgis::SymbolType type )
231 {
232   switch ( type )
233   {
234     case Qgis::SymbolType::Marker:
235       return QObject::tr( "Marker" );
236     case Qgis::SymbolType::Line:
237       return QObject::tr( "Line" );
238     case Qgis::SymbolType::Fill:
239       return QObject::tr( "Fill" );
240     case Qgis::SymbolType::Hybrid:
241       return QObject::tr( "Hybrid" );
242   }
243   return QString();
244 }
245 
symbolTypeForGeometryType(QgsWkbTypes::GeometryType type)246 Qgis::SymbolType QgsSymbol::symbolTypeForGeometryType( QgsWkbTypes::GeometryType type )
247 {
248   switch ( type )
249   {
250     case QgsWkbTypes::PointGeometry:
251       return Qgis::SymbolType::Marker;
252     case QgsWkbTypes::LineGeometry:
253       return Qgis::SymbolType::Line;
254     case QgsWkbTypes::PolygonGeometry:
255       return Qgis::SymbolType::Fill;
256     case QgsWkbTypes::UnknownGeometry:
257     case QgsWkbTypes::NullGeometry:
258       return Qgis::SymbolType::Hybrid;
259   }
260   return Qgis::SymbolType::Hybrid;
261 }
262 
propertyDefinitions()263 const QgsPropertiesDefinition &QgsSymbol::propertyDefinitions()
264 {
265   QgsSymbol::initPropertyDefinitions();
266   return sPropertyDefinitions;
267 }
268 
~QgsSymbol()269 QgsSymbol::~QgsSymbol()
270 {
271   // delete all symbol layers (we own them, so it's okay)
272   qDeleteAll( mLayers );
273 }
274 
outputUnit() const275 QgsUnitTypes::RenderUnit QgsSymbol::outputUnit() const
276 {
277   if ( mLayers.empty() )
278   {
279     return QgsUnitTypes::RenderUnknownUnit;
280   }
281 
282   QgsSymbolLayerList::const_iterator it = mLayers.constBegin();
283 
284   QgsUnitTypes::RenderUnit unit = ( *it )->outputUnit();
285 
286   for ( ; it != mLayers.constEnd(); ++it )
287   {
288     if ( ( *it )->outputUnit() != unit )
289     {
290       return QgsUnitTypes::RenderUnknownUnit;
291     }
292   }
293   return unit;
294 }
295 
usesMapUnits() const296 bool QgsSymbol::usesMapUnits() const
297 {
298   if ( mLayers.empty() )
299   {
300     return false;
301   }
302 
303   for ( const QgsSymbolLayer *layer : mLayers )
304   {
305     if ( layer->usesMapUnits() )
306     {
307       return true;
308     }
309   }
310   return false;
311 }
312 
mapUnitScale() const313 QgsMapUnitScale QgsSymbol::mapUnitScale() const
314 {
315   if ( mLayers.empty() )
316   {
317     return QgsMapUnitScale();
318   }
319 
320   QgsSymbolLayerList::const_iterator it = mLayers.constBegin();
321   if ( it == mLayers.constEnd() )
322     return QgsMapUnitScale();
323 
324   QgsMapUnitScale scale = ( *it )->mapUnitScale();
325   ++it;
326 
327   for ( ; it != mLayers.constEnd(); ++it )
328   {
329     if ( ( *it )->mapUnitScale() != scale )
330     {
331       return QgsMapUnitScale();
332     }
333   }
334   return scale;
335 }
336 
setOutputUnit(QgsUnitTypes::RenderUnit u)337 void QgsSymbol::setOutputUnit( QgsUnitTypes::RenderUnit u )
338 {
339   const auto constMLayers = mLayers;
340   for ( QgsSymbolLayer *layer : constMLayers )
341   {
342     layer->setOutputUnit( u );
343   }
344 }
345 
setMapUnitScale(const QgsMapUnitScale & scale)346 void QgsSymbol::setMapUnitScale( const QgsMapUnitScale &scale )
347 {
348   const auto constMLayers = mLayers;
349   for ( QgsSymbolLayer *layer : constMLayers )
350   {
351     layer->setMapUnitScale( scale );
352   }
353 }
354 
defaultSymbol(QgsWkbTypes::GeometryType geomType)355 QgsSymbol *QgsSymbol::defaultSymbol( QgsWkbTypes::GeometryType geomType )
356 {
357   std::unique_ptr< QgsSymbol > s;
358 
359   // override global default if project has a default for this type
360   QString defaultSymbol;
361   switch ( geomType )
362   {
363     case QgsWkbTypes::PointGeometry :
364       defaultSymbol = QgsProject::instance()->readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Marker" ) );
365       break;
366     case QgsWkbTypes::LineGeometry :
367       defaultSymbol = QgsProject::instance()->readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Line" ) );
368       break;
369     case QgsWkbTypes::PolygonGeometry :
370       defaultSymbol = QgsProject::instance()->readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Fill" ) );
371       break;
372     default:
373       break;
374   }
375   if ( !defaultSymbol.isEmpty() )
376     s.reset( QgsStyle::defaultStyle()->symbol( defaultSymbol ) );
377 
378   // if no default found for this type, get global default (as previously)
379   if ( !s )
380   {
381     switch ( geomType )
382     {
383       case QgsWkbTypes::PointGeometry:
384         s = std::make_unique< QgsMarkerSymbol >();
385         break;
386       case QgsWkbTypes::LineGeometry:
387         s = std::make_unique< QgsLineSymbol >();
388         break;
389       case QgsWkbTypes::PolygonGeometry:
390         s = std::make_unique< QgsFillSymbol >();
391         break;
392       default:
393         QgsDebugMsg( QStringLiteral( "unknown layer's geometry type" ) );
394         return nullptr;
395     }
396   }
397 
398   // set opacity
399   double opacity = 1.0;
400   bool ok = false;
401   // upgrade old setting
402   double alpha = QgsProject::instance()->readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/AlphaInt" ), 255, &ok );
403   if ( ok )
404     opacity = alpha / 255.0;
405   double newOpacity = QgsProject::instance()->readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Opacity" ), 1.0, &ok );
406   if ( ok )
407     opacity = newOpacity;
408   s->setOpacity( opacity );
409 
410   // set random color, it project prefs allow
411   if ( defaultSymbol.isEmpty() ||
412        QgsProject::instance()->readBoolEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ), true ) )
413   {
414     s->setColor( QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor() );
415   }
416 
417   return s.release();
418 }
419 
symbolLayer(int layer)420 QgsSymbolLayer *QgsSymbol::symbolLayer( int layer )
421 {
422   return mLayers.value( layer );
423 }
424 
symbolLayer(int layer) const425 const QgsSymbolLayer *QgsSymbol::symbolLayer( int layer ) const
426 {
427   return mLayers.value( layer );
428 }
429 
insertSymbolLayer(int index,QgsSymbolLayer * layer)430 bool QgsSymbol::insertSymbolLayer( int index, QgsSymbolLayer *layer )
431 {
432   if ( index < 0 || index > mLayers.count() ) // can be added also after the last index
433     return false;
434 
435   if ( !layer || !layer->isCompatibleWithSymbol( this ) )
436     return false;
437 
438   mLayers.insert( index, layer );
439   return true;
440 }
441 
442 
appendSymbolLayer(QgsSymbolLayer * layer)443 bool QgsSymbol::appendSymbolLayer( QgsSymbolLayer *layer )
444 {
445   if ( !layer || !layer->isCompatibleWithSymbol( this ) )
446     return false;
447 
448   mLayers.append( layer );
449   return true;
450 }
451 
452 
deleteSymbolLayer(int index)453 bool QgsSymbol::deleteSymbolLayer( int index )
454 {
455   if ( index < 0 || index >= mLayers.count() )
456     return false;
457 
458   delete mLayers.at( index );
459   mLayers.removeAt( index );
460   return true;
461 }
462 
463 
takeSymbolLayer(int index)464 QgsSymbolLayer *QgsSymbol::takeSymbolLayer( int index )
465 {
466   if ( index < 0 || index >= mLayers.count() )
467     return nullptr;
468 
469   return mLayers.takeAt( index );
470 }
471 
472 
changeSymbolLayer(int index,QgsSymbolLayer * layer)473 bool QgsSymbol::changeSymbolLayer( int index, QgsSymbolLayer *layer )
474 {
475   QgsSymbolLayer *oldLayer = mLayers.value( index );
476 
477   if ( oldLayer == layer )
478     return false;
479 
480   if ( !layer || !layer->isCompatibleWithSymbol( this ) )
481     return false;
482 
483   delete oldLayer; // first delete the original layer
484   mLayers[index] = layer; // set new layer
485   return true;
486 }
487 
488 
startRender(QgsRenderContext & context,const QgsFields & fields)489 void QgsSymbol::startRender( QgsRenderContext &context, const QgsFields &fields )
490 {
491   Q_ASSERT_X( !mStarted, "startRender", "Rendering has already been started for this symbol instance!" );
492   mStarted = true;
493 
494   mSymbolRenderContext.reset( new QgsSymbolRenderContext( context, QgsUnitTypes::RenderUnknownUnit, mOpacity, false, mRenderHints, nullptr, fields ) );
495 
496   // Why do we need a copy here ? Is it to make sure the symbol layer rendering does not mess with the symbol render context ?
497   // Or is there another profound reason ?
498   QgsSymbolRenderContext symbolContext( context, QgsUnitTypes::RenderUnknownUnit, mOpacity, false, mRenderHints, nullptr, fields );
499 
500   std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::updateSymbolScope( this, new QgsExpressionContextScope() ) );
501   mSymbolRenderContext->setExpressionContextScope( scope.release() );
502 
503   mDataDefinedProperties.prepare( context.expressionContext() );
504 
505   const auto constMLayers = mLayers;
506   for ( QgsSymbolLayer *layer : constMLayers )
507   {
508     if ( !layer->enabled() || !context.isSymbolLayerEnabled( layer ) )
509       continue;
510 
511     layer->prepareExpressions( symbolContext );
512     layer->startRender( symbolContext );
513   }
514 }
515 
stopRender(QgsRenderContext & context)516 void QgsSymbol::stopRender( QgsRenderContext &context )
517 {
518   Q_ASSERT_X( mStarted, "startRender", "startRender was not called for this symbol instance!" );
519   mStarted = false;
520 
521   Q_UNUSED( context )
522   if ( mSymbolRenderContext )
523   {
524     const auto constMLayers = mLayers;
525     for ( QgsSymbolLayer *layer : constMLayers )
526     {
527       if ( !layer->enabled()  || !context.isSymbolLayerEnabled( layer ) )
528         continue;
529 
530       layer->stopRender( *mSymbolRenderContext );
531     }
532   }
533 
534   mSymbolRenderContext.reset( nullptr );
535 
536   Q_NOWARN_DEPRECATED_PUSH
537   mLayer = nullptr;
538   Q_NOWARN_DEPRECATED_POP
539 }
540 
setColor(const QColor & color)541 void QgsSymbol::setColor( const QColor &color )
542 {
543   const auto constMLayers = mLayers;
544   for ( QgsSymbolLayer *layer : constMLayers )
545   {
546     if ( !layer->isLocked() )
547       layer->setColor( color );
548   }
549 }
550 
color() const551 QColor QgsSymbol::color() const
552 {
553   for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
554   {
555     // return color of the first unlocked layer
556     if ( !( *it )->isLocked() )
557       return ( *it )->color();
558   }
559   return QColor( 0, 0, 0 );
560 }
561 
drawPreviewIcon(QPainter * painter,QSize size,QgsRenderContext * customContext,bool selected,const QgsExpressionContext * expressionContext,const QgsLegendPatchShape * patchShape)562 void QgsSymbol::drawPreviewIcon( QPainter *painter, QSize size, QgsRenderContext *customContext, bool selected, const QgsExpressionContext *expressionContext, const QgsLegendPatchShape *patchShape )
563 {
564   QgsRenderContext *context = customContext;
565   std::unique_ptr< QgsRenderContext > tempContext;
566   if ( !context )
567   {
568     tempContext.reset( new QgsRenderContext( QgsRenderContext::fromQPainter( painter ) ) );
569     context = tempContext.get();
570     context->setFlag( Qgis::RenderContextFlag::RenderSymbolPreview, true );
571   }
572 
573   const bool prevForceVector = context->forceVectorOutput();
574   context->setForceVectorOutput( true );
575 
576   const double opacity = expressionContext ? dataDefinedProperties().valueAsDouble( QgsSymbol::PropertyOpacity, *expressionContext, mOpacity ) : mOpacity;
577 
578   QgsSymbolRenderContext symbolContext( *context, QgsUnitTypes::RenderUnknownUnit, opacity, false, mRenderHints, nullptr );
579   symbolContext.setSelected( selected );
580   switch ( mType )
581   {
582     case Qgis::SymbolType::Marker:
583       symbolContext.setOriginalGeometryType( QgsWkbTypes::PointGeometry );
584       break;
585     case Qgis::SymbolType::Line:
586       symbolContext.setOriginalGeometryType( QgsWkbTypes::LineGeometry );
587       break;
588     case Qgis::SymbolType::Fill:
589       symbolContext.setOriginalGeometryType( QgsWkbTypes::PolygonGeometry );
590       break;
591     case Qgis::SymbolType::Hybrid:
592       symbolContext.setOriginalGeometryType( QgsWkbTypes::UnknownGeometry );
593       break;
594   }
595 
596   if ( patchShape )
597     symbolContext.setPatchShape( *patchShape );
598 
599   if ( !customContext && expressionContext )
600   {
601     context->setExpressionContext( *expressionContext );
602   }
603   else if ( !customContext )
604   {
605     // if no render context was passed, build a minimal expression context
606     QgsExpressionContext expContext;
607     expContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) );
608     context->setExpressionContext( expContext );
609   }
610 
611   for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
612   {
613     if ( !layer->enabled()  || ( customContext && !customContext->isSymbolLayerEnabled( layer ) ) )
614       continue;
615 
616     if ( mType == Qgis::SymbolType::Fill && layer->type() == Qgis::SymbolType::Line )
617     {
618       // line symbol layer would normally draw just a line
619       // so we override this case to force it to draw a polygon stroke
620       QgsLineSymbolLayer *lsl = dynamic_cast<QgsLineSymbolLayer *>( layer );
621       if ( lsl )
622       {
623         // from QgsFillSymbolLayer::drawPreviewIcon() -- would be nicer to add the
624         // symbol type to QgsSymbolLayer::drawPreviewIcon so this logic could be avoided!
625 
626         // hmm... why was this using size -1 ??
627         const QSizeF targetSize = QSizeF( size.width() - 1, size.height() - 1 );
628 
629         const QList< QList< QPolygonF > > polys = patchShape ? patchShape->toQPolygonF( Qgis::SymbolType::Fill, targetSize )
630             : QgsStyle::defaultStyle()->defaultPatchAsQPolygonF( Qgis::SymbolType::Fill, targetSize );
631 
632         lsl->startRender( symbolContext );
633         QgsPaintEffect *effect = lsl->paintEffect();
634 
635         std::unique_ptr< QgsEffectPainter > effectPainter;
636         if ( effect && effect->enabled() )
637           effectPainter = std::make_unique< QgsEffectPainter >( symbolContext.renderContext(), effect );
638 
639         for ( const QList< QPolygonF > &poly : polys )
640         {
641           QVector< QPolygonF > rings;
642           rings.reserve( poly.size() );
643           for ( int i = 1; i < poly.size(); ++i )
644             rings << poly.at( i );
645           lsl->renderPolygonStroke( poly.value( 0 ), &rings, symbolContext );
646         }
647 
648         effectPainter.reset();
649         lsl->stopRender( symbolContext );
650       }
651     }
652     else
653       layer->drawPreviewIcon( symbolContext, size );
654   }
655 
656   context->setForceVectorOutput( prevForceVector );
657 }
658 
exportImage(const QString & path,const QString & format,QSize size)659 void QgsSymbol::exportImage( const QString &path, const QString &format, QSize size )
660 {
661   if ( format.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
662   {
663     QSvgGenerator generator;
664     generator.setFileName( path );
665     generator.setSize( size );
666     generator.setViewBox( QRect( 0, 0, size.height(), size.height() ) );
667 
668     QPainter painter( &generator );
669     drawPreviewIcon( &painter, size );
670     painter.end();
671   }
672   else
673   {
674     QImage image = asImage( size );
675     image.save( path );
676   }
677 }
678 
asImage(QSize size,QgsRenderContext * customContext)679 QImage QgsSymbol::asImage( QSize size, QgsRenderContext *customContext )
680 {
681   QImage image( size, QImage::Format_ARGB32_Premultiplied );
682   image.fill( 0 );
683 
684   QPainter p( &image );
685   p.setRenderHint( QPainter::Antialiasing );
686 
687   drawPreviewIcon( &p, size, customContext );
688 
689   return image;
690 }
691 
692 
bigSymbolPreviewImage(QgsExpressionContext * expressionContext,Qgis::SymbolPreviewFlags flags)693 QImage QgsSymbol::bigSymbolPreviewImage( QgsExpressionContext *expressionContext, Qgis::SymbolPreviewFlags flags )
694 {
695   QImage preview( QSize( 100, 100 ), QImage::Format_ARGB32_Premultiplied );
696   preview.fill( 0 );
697 
698   QPainter p( &preview );
699   p.setRenderHint( QPainter::Antialiasing );
700   p.translate( 0.5, 0.5 ); // shift by half a pixel to avoid blurring due antialiasing
701 
702   if ( mType == Qgis::SymbolType::Marker && flags & Qgis::SymbolPreviewFlag::FlagIncludeCrosshairsForMarkerSymbols )
703   {
704     p.setPen( QPen( Qt::gray ) );
705     p.drawLine( 0, 50, 100, 50 );
706     p.drawLine( 50, 0, 50, 100 );
707   }
708 
709   QgsRenderContext context = QgsRenderContext::fromQPainter( &p );
710   context.setFlag( Qgis::RenderContextFlag::RenderSymbolPreview );
711   if ( expressionContext )
712     context.setExpressionContext( *expressionContext );
713 
714   context.setIsGuiPreview( true );
715   startRender( context );
716 
717   if ( mType == Qgis::SymbolType::Line )
718   {
719     QPolygonF poly;
720     poly << QPointF( 0, 50 ) << QPointF( 99, 50 );
721     static_cast<QgsLineSymbol *>( this )->renderPolyline( poly, nullptr, context );
722   }
723   else if ( mType == Qgis::SymbolType::Fill )
724   {
725     QPolygonF polygon;
726     polygon << QPointF( 20, 20 ) << QPointF( 80, 20 ) << QPointF( 80, 80 ) << QPointF( 20, 80 ) << QPointF( 20, 20 );
727     static_cast<QgsFillSymbol *>( this )->renderPolygon( polygon, nullptr, nullptr, context );
728   }
729   else // marker
730   {
731     static_cast<QgsMarkerSymbol *>( this )->renderPoint( QPointF( 50, 50 ), nullptr, context );
732   }
733 
734   stopRender( context );
735   return preview;
736 }
737 
bigSymbolPreviewImage(QgsExpressionContext * expressionContext,int flags)738 QImage QgsSymbol::bigSymbolPreviewImage( QgsExpressionContext *expressionContext, int flags )
739 {
740   return bigSymbolPreviewImage( expressionContext, static_cast< Qgis::SymbolPreviewFlags >( flags ) );
741 }
742 
dump() const743 QString QgsSymbol::dump() const
744 {
745   QString t;
746   switch ( type() )
747   {
748     case Qgis::SymbolType::Marker:
749       t = QStringLiteral( "MARKER" );
750       break;
751     case Qgis::SymbolType::Line:
752       t = QStringLiteral( "LINE" );
753       break;
754     case Qgis::SymbolType::Fill:
755       t = QStringLiteral( "FILL" );
756       break;
757     default:
758       Q_ASSERT( false && "unknown symbol type" );
759   }
760   QString s = QStringLiteral( "%1 SYMBOL (%2 layers) color %3" ).arg( t ).arg( mLayers.count() ).arg( QgsSymbolLayerUtils::encodeColor( color() ) );
761 
762   for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
763   {
764     // TODO:
765   }
766   return s;
767 }
768 
toSld(QDomDocument & doc,QDomElement & element,QVariantMap props) const769 void QgsSymbol::toSld( QDomDocument &doc, QDomElement &element, QVariantMap props ) const
770 {
771   props[ QStringLiteral( "alpha" )] = QString::number( opacity() );
772   double scaleFactor = 1.0;
773   props[ QStringLiteral( "uom" )] = QgsSymbolLayerUtils::encodeSldUom( outputUnit(), &scaleFactor );
774   props[ QStringLiteral( "uomScale" )] = ( !qgsDoubleNear( scaleFactor, 1.0 ) ? qgsDoubleToString( scaleFactor ) : QString() );
775 
776   for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
777   {
778     ( *it )->toSld( doc, element, props );
779   }
780 }
781 
cloneLayers() const782 QgsSymbolLayerList QgsSymbol::cloneLayers() const
783 {
784   QgsSymbolLayerList lst;
785   for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
786   {
787     QgsSymbolLayer *layer = ( *it )->clone();
788     layer->setLocked( ( *it )->isLocked() );
789     layer->setRenderingPass( ( *it )->renderingPass() );
790     layer->setEnabled( ( *it )->enabled() );
791     lst.append( layer );
792   }
793   return lst;
794 }
795 
renderUsingLayer(QgsSymbolLayer * layer,QgsSymbolRenderContext & context,QgsWkbTypes::GeometryType geometryType,const QPolygonF * points,const QVector<QPolygonF> * rings)796 void QgsSymbol::renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, QgsWkbTypes::GeometryType geometryType, const QPolygonF *points, const QVector<QPolygonF> *rings )
797 {
798   Q_ASSERT( layer->type() == Qgis::SymbolType::Hybrid );
799 
800   if ( layer->dataDefinedProperties().hasActiveProperties() && !layer->dataDefinedProperties().valueAsBool( QgsSymbolLayer::PropertyLayerEnabled, context.renderContext().expressionContext(), true ) )
801     return;
802 
803   QgsGeometryGeneratorSymbolLayer *generatorLayer = static_cast<QgsGeometryGeneratorSymbolLayer *>( layer );
804 
805   QgsPaintEffect *effect = generatorLayer->paintEffect();
806   if ( effect && effect->enabled() )
807   {
808     QgsEffectPainter p( context.renderContext(), effect );
809     generatorLayer->render( context, geometryType, points, rings );
810   }
811   else
812   {
813     generatorLayer->render( context, geometryType, points, rings );
814   }
815 }
816 
usedAttributes(const QgsRenderContext & context) const817 QSet<QString> QgsSymbol::usedAttributes( const QgsRenderContext &context ) const
818 {
819   // calling referencedFields() with ignoreContext=true because in our expression context
820   // we do not have valid QgsFields yet - because of that the field names from expressions
821   // wouldn't get reported
822   QSet<QString> attributes = mDataDefinedProperties.referencedFields( context.expressionContext(), true );
823   QgsSymbolLayerList::const_iterator sIt = mLayers.constBegin();
824   for ( ; sIt != mLayers.constEnd(); ++sIt )
825   {
826     if ( *sIt )
827     {
828       attributes.unite( ( *sIt )->usedAttributes( context ) );
829     }
830   }
831   return attributes;
832 }
833 
setDataDefinedProperty(QgsSymbol::Property key,const QgsProperty & property)834 void QgsSymbol::setDataDefinedProperty( QgsSymbol::Property key, const QgsProperty &property )
835 {
836   mDataDefinedProperties.setProperty( key, property );
837 }
838 
hasDataDefinedProperties() const839 bool QgsSymbol::hasDataDefinedProperties() const
840 {
841   if ( mDataDefinedProperties.hasActiveProperties() )
842     return true;
843 
844   for ( QgsSymbolLayer *layer : mLayers )
845   {
846     if ( layer->hasDataDefinedProperties() )
847       return true;
848   }
849   return false;
850 }
851 
canCauseArtifactsBetweenAdjacentTiles() const852 bool QgsSymbol::canCauseArtifactsBetweenAdjacentTiles() const
853 {
854   for ( QgsSymbolLayer *layer : mLayers )
855   {
856     if ( layer->canCauseArtifactsBetweenAdjacentTiles() )
857       return true;
858   }
859   return false;
860 }
861 
setLayer(const QgsVectorLayer * layer)862 void QgsSymbol::setLayer( const QgsVectorLayer *layer )
863 {
864   Q_NOWARN_DEPRECATED_PUSH
865   mLayer = layer;
866   Q_NOWARN_DEPRECATED_POP
867 }
868 
layer() const869 const QgsVectorLayer *QgsSymbol::layer() const
870 {
871   Q_NOWARN_DEPRECATED_PUSH
872   return mLayer;
873   Q_NOWARN_DEPRECATED_POP
874 }
875 
876 ///@cond PRIVATE
877 
878 /**
879  * RAII class to pop scope from an expression context on destruction
880  */
881 class ExpressionContextScopePopper
882 {
883   public:
884 
885     ExpressionContextScopePopper() = default;
886 
~ExpressionContextScopePopper()887     ~ExpressionContextScopePopper()
888     {
889       if ( context )
890         context->popScope();
891     }
892 
893     QgsExpressionContext *context = nullptr;
894 };
895 
896 /**
897  * RAII class to restore original geometry on a render context on destruction
898  */
899 class GeometryRestorer
900 {
901   public:
GeometryRestorer(QgsRenderContext & context)902     GeometryRestorer( QgsRenderContext &context )
903       : mContext( context ),
904         mGeometry( context.geometry() )
905     {}
906 
~GeometryRestorer()907     ~GeometryRestorer()
908     {
909       mContext.setGeometry( mGeometry );
910     }
911 
912   private:
913     QgsRenderContext &mContext;
914     const QgsAbstractGeometry *mGeometry;
915 };
916 ///@endcond PRIVATE
917 
renderFeature(const QgsFeature & feature,QgsRenderContext & context,int layer,bool selected,bool drawVertexMarker,Qgis::VertexMarkerType currentVertexMarkerType,double currentVertexMarkerSize)918 void QgsSymbol::renderFeature( const QgsFeature &feature, QgsRenderContext &context, int layer, bool selected, bool drawVertexMarker, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize )
919 {
920   if ( context.renderingStopped() )
921     return;
922 
923   const QgsGeometry geom = feature.geometry();
924   if ( geom.isNull() )
925   {
926     return;
927   }
928 
929   GeometryRestorer geomRestorer( context );
930 
931   bool usingSegmentizedGeometry = false;
932   context.setGeometry( geom.constGet() );
933 
934   if ( geom.type() != QgsWkbTypes::PointGeometry && !geom.boundingBox().isNull() )
935   {
936     try
937     {
938       const QPointF boundsOrigin = _getPoint( context, QgsPoint( geom.boundingBox().xMinimum(), geom.boundingBox().yMinimum() ) );
939       if ( std::isfinite( boundsOrigin.x() ) && std::isfinite( boundsOrigin.y() ) )
940         context.setTextureOrigin( boundsOrigin );
941     }
942     catch ( QgsCsException & )
943     {
944 
945     }
946   }
947 
948   bool clippingEnabled = clipFeaturesToExtent();
949   // do any symbol layers prevent feature clipping?
950   for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
951   {
952     if ( layer->flags() & Qgis::SymbolLayerFlag::DisableFeatureClipping )
953     {
954       clippingEnabled = false;
955       break;
956     }
957   }
958   if ( clippingEnabled && context.testFlag( Qgis::RenderContextFlag::RenderMapTile ) )
959   {
960     // If the "avoid artifacts between adjacent tiles" flag is set (RenderMapTile), then we'll force disable
961     // the geometry clipping IF (and only if) this symbol can potentially have rendering artifacts when rendered as map tiles.
962     // If the symbol won't have any artifacts anyway, then it's pointless and incredibly expensive to skip the clipping!
963     if ( canCauseArtifactsBetweenAdjacentTiles() )
964     {
965       clippingEnabled = false;
966     }
967   }
968   if ( context.extent().isEmpty() )
969     clippingEnabled = false;
970 
971   mSymbolRenderContext->setGeometryPartCount( geom.constGet()->partCount() );
972   mSymbolRenderContext->setGeometryPartNum( 1 );
973 
974   const bool needsExpressionContext = hasDataDefinedProperties();
975   ExpressionContextScopePopper scopePopper;
976   if ( mSymbolRenderContext->expressionContextScope() )
977   {
978     if ( needsExpressionContext )
979     {
980       // this is somewhat nasty - by appending this scope here it's now owned
981       // by both mSymbolRenderContext AND context.expressionContext()
982       // the RAII scopePopper is required to make sure it always has ownership transferred back
983       // from context.expressionContext(), even if exceptions of other early exits occur in this
984       // function
985       context.expressionContext().appendScope( mSymbolRenderContext->expressionContextScope() );
986       scopePopper.context = &context.expressionContext();
987 
988       QgsExpressionContextUtils::updateSymbolScope( this, mSymbolRenderContext->expressionContextScope() );
989       mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_COUNT, mSymbolRenderContext->geometryPartCount(), true ) );
990       mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, 1, true ) );
991     }
992   }
993 
994   // Collection of markers to paint, only used for no curve types.
995   QPolygonF markers;
996 
997   QgsGeometry renderedBoundsGeom;
998 
999   // Step 1 - collect the set of painter coordinate geometries to render.
1000   // We do this upfront, because we only want to ever do this once, regardless how many symbol layers we need to render.
1001 
1002   struct PointInfo
1003   {
1004     QPointF renderPoint;
1005     const QgsPoint *originalGeometry = nullptr;
1006   };
1007   QVector< PointInfo > pointsToRender;
1008 
1009   struct LineInfo
1010   {
1011     QPolygonF renderLine;
1012     const QgsCurve *originalGeometry = nullptr;
1013   };
1014   QVector< LineInfo > linesToRender;
1015 
1016   struct PolygonInfo
1017   {
1018     QPolygonF renderExterior;
1019     QVector< QPolygonF > renderRings;
1020     const QgsCurvePolygon *originalGeometry = nullptr;
1021     int originalPartIndex = 0;
1022   };
1023   QVector< PolygonInfo > polygonsToRender;
1024 
1025   std::function< void ( const QgsAbstractGeometry *, int partIndex )> getPartGeometry;
1026   getPartGeometry = [&pointsToRender, &linesToRender, &polygonsToRender, &getPartGeometry, &context, &clippingEnabled, &markers, &feature, &usingSegmentizedGeometry, this]( const QgsAbstractGeometry * part, int partIndex = 0 )
1027   {
1028     Q_UNUSED( feature )
1029 
1030     if ( !part )
1031       return;
1032 
1033     // geometry preprocessing
1034     QgsGeometry temporaryGeometryContainer;
1035     const QgsAbstractGeometry *processedGeometry = nullptr;
1036 
1037     const bool isMultiPart = qgsgeometry_cast< const QgsGeometryCollection * >( part ) && qgsgeometry_cast< const QgsGeometryCollection * >( part )->numGeometries() > 1;
1038 
1039     if ( !isMultiPart )
1040     {
1041       // segmentize curved geometries
1042       const bool needsSegmentizing = QgsWkbTypes::isCurvedType( part->wkbType() ) || part->hasCurvedSegments();
1043       if ( needsSegmentizing )
1044       {
1045         std::unique_ptr< QgsAbstractGeometry > segmentizedPart( part->segmentize( context.segmentationTolerance(), context.segmentationToleranceType() ) );
1046         if ( !segmentizedPart )
1047         {
1048           return;
1049         }
1050         temporaryGeometryContainer.set( segmentizedPart.release() );
1051         processedGeometry = temporaryGeometryContainer.constGet();
1052         usingSegmentizedGeometry = true;
1053       }
1054       else
1055       {
1056         // no segmentation required
1057         processedGeometry = part;
1058       }
1059 
1060       // Simplify the geometry, if needed.
1061       if ( context.vectorSimplifyMethod().forceLocalOptimization() )
1062       {
1063         const int simplifyHints = context.vectorSimplifyMethod().simplifyHints();
1064         const QgsMapToPixelSimplifier simplifier( simplifyHints, context.vectorSimplifyMethod().tolerance(),
1065             static_cast< QgsMapToPixelSimplifier::SimplifyAlgorithm >( context.vectorSimplifyMethod().simplifyAlgorithm() ) );
1066 
1067         std::unique_ptr< QgsAbstractGeometry > simplified( simplifier.simplify( processedGeometry ) );
1068         if ( simplified )
1069         {
1070           temporaryGeometryContainer.set( simplified.release() );
1071           processedGeometry = temporaryGeometryContainer.constGet();
1072         }
1073       }
1074 
1075       // clip geometry to render context clipping regions
1076       if ( !context.featureClipGeometry().isEmpty() )
1077       {
1078         // apply feature clipping from context to the rendered geometry only -- just like the render time simplification,
1079         // we should NEVER apply this to the geometry attached to the feature itself. Doing so causes issues with certain
1080         // renderer settings, e.g. if polygons are being rendered using a rule based renderer based on the feature's area,
1081         // then we need to ensure that the original feature area is used instead of the clipped area..
1082         QgsGeos geos( processedGeometry );
1083         std::unique_ptr< QgsAbstractGeometry > clippedGeom( geos.intersection( context.featureClipGeometry().constGet() ) );
1084         if ( clippedGeom )
1085         {
1086           temporaryGeometryContainer.set( clippedGeom.release() );
1087           processedGeometry = temporaryGeometryContainer.constGet();
1088         }
1089       }
1090     }
1091     else
1092     {
1093       // for multipart geometries, the processing is deferred till we're rendering the actual part...
1094       processedGeometry = part;
1095     }
1096 
1097     if ( !processedGeometry )
1098     {
1099       // shouldn't happen!
1100       QgsDebugMsg( QStringLiteral( "No processed geometry to render for part!" ) );
1101       return;
1102     }
1103 
1104     switch ( QgsWkbTypes::flatType( processedGeometry->wkbType() ) )
1105     {
1106       case QgsWkbTypes::Point:
1107       {
1108         if ( mType != Qgis::SymbolType::Marker )
1109         {
1110           QgsDebugMsgLevel( QStringLiteral( "point can be drawn only with marker symbol!" ), 2 );
1111           break;
1112         }
1113 
1114         PointInfo info;
1115         info.originalGeometry = qgsgeometry_cast< const QgsPoint * >( part );
1116         info.renderPoint = _getPoint( context, *info.originalGeometry );
1117         pointsToRender << info;
1118         break;
1119       }
1120 
1121       case QgsWkbTypes::LineString:
1122       {
1123         if ( mType != Qgis::SymbolType::Line )
1124         {
1125           QgsDebugMsgLevel( QStringLiteral( "linestring can be drawn only with line symbol!" ), 2 );
1126           break;
1127         }
1128 
1129         LineInfo info;
1130         info.originalGeometry = qgsgeometry_cast<const QgsCurve *>( part );
1131         info.renderLine = _getLineString( context, *qgsgeometry_cast<const QgsCurve *>( processedGeometry ), clippingEnabled );
1132         linesToRender << info;
1133         break;
1134       }
1135 
1136       case QgsWkbTypes::Polygon:
1137       case QgsWkbTypes::Triangle:
1138       {
1139         QPolygonF pts;
1140         if ( mType != Qgis::SymbolType::Fill )
1141         {
1142           QgsDebugMsgLevel( QStringLiteral( "polygon can be drawn only with fill symbol!" ), 2 );
1143           break;
1144         }
1145 
1146         PolygonInfo info;
1147         info.originalGeometry = qgsgeometry_cast<const QgsCurvePolygon *>( part );
1148         info.originalPartIndex = partIndex;
1149         if ( !qgsgeometry_cast<const QgsPolygon *>( processedGeometry )->exteriorRing() )
1150         {
1151           QgsDebugMsg( QStringLiteral( "cannot render polygon with no exterior ring" ) );
1152           break;
1153         }
1154 
1155         _getPolygon( info.renderExterior, info.renderRings, context, *qgsgeometry_cast<const QgsPolygon *>( processedGeometry ), clippingEnabled, mForceRHR );
1156         polygonsToRender << info;
1157         break;
1158       }
1159 
1160       case QgsWkbTypes::MultiPoint:
1161       {
1162         const QgsMultiPoint *mp = qgsgeometry_cast< const QgsMultiPoint * >( processedGeometry );
1163         markers.reserve( mp->numGeometries() );
1164       }
1165       FALLTHROUGH
1166       case QgsWkbTypes::MultiCurve:
1167       case QgsWkbTypes::MultiLineString:
1168       case QgsWkbTypes::GeometryCollection:
1169       {
1170         const QgsGeometryCollection *geomCollection = qgsgeometry_cast<const QgsGeometryCollection *>( processedGeometry );
1171 
1172         const unsigned int num = geomCollection->numGeometries();
1173         for ( unsigned int i = 0; i < num; ++i )
1174         {
1175           if ( context.renderingStopped() )
1176             break;
1177 
1178           getPartGeometry( geomCollection->geometryN( i ), i );
1179         }
1180         break;
1181       }
1182 
1183       case QgsWkbTypes::MultiSurface:
1184       case QgsWkbTypes::MultiPolygon:
1185       {
1186         if ( mType != Qgis::SymbolType::Fill )
1187         {
1188           QgsDebugMsgLevel( QStringLiteral( "multi-polygon can be drawn only with fill symbol!" ), 2 );
1189           break;
1190         }
1191 
1192         QPolygonF pts;
1193 
1194         const QgsGeometryCollection *geomCollection = dynamic_cast<const QgsGeometryCollection *>( processedGeometry );
1195         const unsigned int num = geomCollection->numGeometries();
1196 
1197         // Sort components by approximate area (probably a bit faster than using
1198         // area() )
1199         std::map<double, QList<unsigned int> > thisAreaToPartNum;
1200         for ( unsigned int i = 0; i < num; ++i )
1201         {
1202           const QgsRectangle r( geomCollection->geometryN( i )->boundingBox() );
1203           thisAreaToPartNum[ r.width() * r.height()] << i;
1204         }
1205 
1206         // Draw starting with larger parts down to smaller parts, so that in
1207         // case of a part being incorrectly inside another part, it is drawn
1208         // on top of it (#15419)
1209         std::map<double, QList<unsigned int> >::const_reverse_iterator iter = thisAreaToPartNum.rbegin();
1210         for ( ; iter != thisAreaToPartNum.rend(); ++iter )
1211         {
1212           const QList<unsigned int> &listPartIndex = iter->second;
1213           for ( int idx = 0; idx < listPartIndex.size(); ++idx )
1214           {
1215             const unsigned i = listPartIndex[idx];
1216             getPartGeometry( geomCollection->geometryN( i ), i );
1217           }
1218         }
1219         break;
1220       }
1221 
1222       default:
1223         QgsDebugMsg( QStringLiteral( "feature %1: unsupported wkb type %2/%3 for rendering" )
1224                      .arg( feature.id() )
1225                      .arg( QgsWkbTypes::displayString( part->wkbType() ) )
1226                      .arg( part->wkbType(), 0, 16 ) );
1227     }
1228   };
1229 
1230   // Use the simplified type ref when rendering -- this avoids some unnecessary cloning/geometry modification
1231   // (e.g. if the original geometry is a compound curve containing only a linestring curve, we don't have
1232   // to segmentize the geometry before rendering)
1233   getPartGeometry( geom.constGet()->simplifiedTypeRef(), 0 );
1234 
1235   // step 2 - determine which layers to render
1236   std::vector< int > layers;
1237   if ( layer == -1 )
1238   {
1239     layers.reserve( mLayers.count() );
1240     for ( int i = 0; i < mLayers.count(); ++i )
1241       layers.emplace_back( i );
1242   }
1243   else
1244   {
1245     layers.emplace_back( layer );
1246   }
1247 
1248   // step 3 - render these geometries using the desired symbol layers.
1249 
1250   if ( needsExpressionContext )
1251     mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_layer_count" ), mLayers.count(), true ) );
1252 
1253   for ( const int symbolLayerIndex : layers )
1254   {
1255     QgsSymbolLayer *symbolLayer = mLayers.value( symbolLayerIndex );
1256     if ( !symbolLayer || !symbolLayer->enabled() )
1257       continue;
1258 
1259     if ( needsExpressionContext )
1260       mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_layer_index" ), symbolLayerIndex + 1, true ) );
1261 
1262     symbolLayer->startFeatureRender( feature, context );
1263 
1264     switch ( mType )
1265     {
1266       case Qgis::SymbolType::Marker:
1267       {
1268         int geometryPartNumber = 0;
1269         for ( const PointInfo &point : std::as_const( pointsToRender ) )
1270         {
1271           if ( context.renderingStopped() )
1272             break;
1273 
1274           mSymbolRenderContext->setGeometryPartNum( geometryPartNumber + 1 );
1275           if ( needsExpressionContext )
1276             mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, geometryPartNumber + 1, true ) );
1277 
1278           static_cast<QgsMarkerSymbol *>( this )->renderPoint( point.renderPoint, &feature, context, symbolLayerIndex, selected );
1279           geometryPartNumber++;
1280         }
1281 
1282         break;
1283       }
1284 
1285       case Qgis::SymbolType::Line:
1286       {
1287         if ( linesToRender.empty() )
1288           break;
1289 
1290         int geometryPartNumber = 0;
1291         for ( const LineInfo &line : std::as_const( linesToRender ) )
1292         {
1293           if ( context.renderingStopped() )
1294             break;
1295 
1296           mSymbolRenderContext->setGeometryPartNum( geometryPartNumber + 1 );
1297           if ( needsExpressionContext )
1298             mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, geometryPartNumber + 1, true ) );
1299 
1300           context.setGeometry( line.originalGeometry );
1301           static_cast<QgsLineSymbol *>( this )->renderPolyline( line.renderLine, &feature, context, symbolLayerIndex, selected );
1302           geometryPartNumber++;
1303         }
1304         break;
1305       }
1306 
1307       case Qgis::SymbolType::Fill:
1308       {
1309         for ( const PolygonInfo &info : std::as_const( polygonsToRender ) )
1310         {
1311           if ( context.renderingStopped() )
1312             break;
1313 
1314           mSymbolRenderContext->setGeometryPartNum( info.originalPartIndex + 1 );
1315           if ( needsExpressionContext )
1316             mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, info.originalPartIndex + 1, true ) );
1317 
1318           context.setGeometry( info.originalGeometry );
1319           static_cast<QgsFillSymbol *>( this )->renderPolygon( info.renderExterior, ( !info.renderRings.isEmpty() ? &info.renderRings : nullptr ), &feature, context, symbolLayerIndex, selected );
1320         }
1321 
1322         break;
1323       }
1324 
1325       case Qgis::SymbolType::Hybrid:
1326         break;
1327     }
1328 
1329     symbolLayer->stopFeatureRender( feature, context );
1330   }
1331 
1332   // step 4 - handle post processing steps
1333   switch ( mType )
1334   {
1335     case Qgis::SymbolType::Marker:
1336     {
1337       markers.reserve( pointsToRender.size() );
1338       for ( const PointInfo &info : std::as_const( pointsToRender ) )
1339       {
1340         if ( context.hasRenderedFeatureHandlers() || context.testFlag( Qgis::RenderContextFlag::DrawSymbolBounds ) )
1341         {
1342           const QRectF bounds = static_cast<QgsMarkerSymbol *>( this )->bounds( info.renderPoint, context, feature );
1343           if ( context.hasRenderedFeatureHandlers() )
1344           {
1345             renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromRect( bounds )
1346                                  : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromRect( QgsRectangle( bounds ) ) << renderedBoundsGeom );
1347           }
1348           if ( context.testFlag( Qgis::RenderContextFlag::DrawSymbolBounds ) )
1349           {
1350             //draw debugging rect
1351             context.painter()->setPen( Qt::red );
1352             context.painter()->setBrush( QColor( 255, 0, 0, 100 ) );
1353             context.painter()->drawRect( bounds );
1354           }
1355         }
1356 
1357         if ( drawVertexMarker && !usingSegmentizedGeometry )
1358         {
1359           markers.append( info.renderPoint );
1360         }
1361       }
1362       break;
1363     }
1364 
1365     case Qgis::SymbolType::Line:
1366     {
1367       for ( const LineInfo &info : std::as_const( linesToRender ) )
1368       {
1369         if ( context.hasRenderedFeatureHandlers() && !info.renderLine.empty() )
1370         {
1371           renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromQPolygonF( info.renderLine )
1372                                : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromQPolygonF( info.renderLine ) << renderedBoundsGeom );
1373         }
1374 
1375         if ( drawVertexMarker && !usingSegmentizedGeometry )
1376         {
1377           markers << info.renderLine;
1378         }
1379       }
1380       break;
1381     }
1382 
1383     case Qgis::SymbolType::Fill:
1384     {
1385       int i = 0;
1386       for ( const PolygonInfo &info : std::as_const( polygonsToRender ) )
1387       {
1388         if ( context.hasRenderedFeatureHandlers() && !info.renderExterior.empty() )
1389         {
1390           renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromQPolygonF( info.renderExterior )
1391                                : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromQPolygonF( info.renderExterior ) << renderedBoundsGeom );
1392           // TODO: consider holes?
1393         }
1394 
1395         if ( drawVertexMarker && !usingSegmentizedGeometry )
1396         {
1397           markers << info.renderExterior;
1398 
1399           for ( const QPolygonF &hole : info.renderRings )
1400           {
1401             markers << hole;
1402           }
1403         }
1404         i++;
1405       }
1406       break;
1407     }
1408 
1409     case Qgis::SymbolType::Hybrid:
1410       break;
1411   }
1412 
1413   if ( context.hasRenderedFeatureHandlers() && !renderedBoundsGeom.isNull() )
1414   {
1415     QgsRenderedFeatureHandlerInterface::RenderedFeatureContext featureContext( context );
1416     const QList< QgsRenderedFeatureHandlerInterface * > handlers = context.renderedFeatureHandlers();
1417     for ( QgsRenderedFeatureHandlerInterface *handler : handlers )
1418       handler->handleRenderedFeature( feature, renderedBoundsGeom, featureContext );
1419   }
1420 
1421   if ( drawVertexMarker )
1422   {
1423     if ( !markers.isEmpty() && !context.renderingStopped() )
1424     {
1425       const auto constMarkers = markers;
1426       for ( QPointF marker : constMarkers )
1427       {
1428         renderVertexMarker( marker, context, currentVertexMarkerType, currentVertexMarkerSize );
1429       }
1430     }
1431     else
1432     {
1433       QgsCoordinateTransform ct = context.coordinateTransform();
1434       const QgsMapToPixel &mtp = context.mapToPixel();
1435 
1436       QgsPoint vertexPoint;
1437       QgsVertexId vertexId;
1438       double x, y, z;
1439       QPointF mapPoint;
1440       while ( geom.constGet()->nextVertex( vertexId, vertexPoint ) )
1441       {
1442         //transform
1443         x = vertexPoint.x();
1444         y = vertexPoint.y();
1445         z = 0.0;
1446         if ( ct.isValid() )
1447         {
1448           ct.transformInPlace( x, y, z );
1449         }
1450         mapPoint.setX( x );
1451         mapPoint.setY( y );
1452         mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1453         renderVertexMarker( mapPoint, context, currentVertexMarkerType, currentVertexMarkerSize );
1454       }
1455     }
1456   }
1457 }
1458 
symbolRenderContext()1459 QgsSymbolRenderContext *QgsSymbol::symbolRenderContext()
1460 {
1461   return mSymbolRenderContext.get();
1462 }
1463 
renderVertexMarker(QPointF pt,QgsRenderContext & context,Qgis::VertexMarkerType currentVertexMarkerType,double currentVertexMarkerSize)1464 void QgsSymbol::renderVertexMarker( QPointF pt, QgsRenderContext &context, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize )
1465 {
1466   int markerSize = context.convertToPainterUnits( currentVertexMarkerSize, QgsUnitTypes::RenderMillimeters );
1467   QgsSymbolLayerUtils::drawVertexMarker( pt.x(), pt.y(), *context.painter(), currentVertexMarkerType, markerSize );
1468 }
1469 
initPropertyDefinitions()1470 void QgsSymbol::initPropertyDefinitions()
1471 {
1472   if ( !sPropertyDefinitions.isEmpty() )
1473     return;
1474 
1475   QString origin = QStringLiteral( "symbol" );
1476 
1477   sPropertyDefinitions = QgsPropertiesDefinition
1478   {
1479     { QgsSymbol::PropertyOpacity, QgsPropertyDefinition( "alpha", QObject::tr( "Opacity" ), QgsPropertyDefinition::Opacity, origin )},
1480   };
1481 }
1482 
startFeatureRender(const QgsFeature & feature,QgsRenderContext & context,const int layer)1483 void QgsSymbol::startFeatureRender( const QgsFeature &feature, QgsRenderContext &context, const int layer )
1484 {
1485   if ( layer != -1 )
1486   {
1487     QgsSymbolLayer *symbolLayer = mLayers.value( layer );
1488     if ( symbolLayer && symbolLayer->enabled() )
1489     {
1490       symbolLayer->startFeatureRender( feature, context );
1491     }
1492     return;
1493   }
1494   else
1495   {
1496     const QList< QgsSymbolLayer * > layers = mLayers;
1497     for ( QgsSymbolLayer *symbolLayer : layers )
1498     {
1499       if ( !symbolLayer->enabled() )
1500         continue;
1501 
1502       symbolLayer->startFeatureRender( feature, context );
1503     }
1504   }
1505 }
1506 
stopFeatureRender(const QgsFeature & feature,QgsRenderContext & context,int layer)1507 void QgsSymbol::stopFeatureRender( const QgsFeature &feature, QgsRenderContext &context, int layer )
1508 {
1509   if ( layer != -1 )
1510   {
1511     QgsSymbolLayer *symbolLayer = mLayers.value( layer );
1512     if ( symbolLayer && symbolLayer->enabled() )
1513     {
1514       symbolLayer->stopFeatureRender( feature, context );
1515     }
1516     return;
1517   }
1518   else
1519   {
1520     const QList< QgsSymbolLayer * > layers = mLayers;
1521     for ( QgsSymbolLayer *symbolLayer : layers )
1522     {
1523       if ( !symbolLayer->enabled() )
1524         continue;
1525 
1526       symbolLayer->stopFeatureRender( feature, context );
1527     }
1528   }
1529 }
1530