QGIS API Documentation  3.27.0-Master (11ef3e5184)
qgsmarkersymbollayer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmarkersymbollayer.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 "qgsmarkersymbollayer.h"
17 #include "qgssymbollayerutils.h"
18 
19 #include "qgsdxfexport.h"
20 #include "qgsdxfpaintdevice.h"
21 #include "qgsexpression.h"
22 #include "qgsfontutils.h"
23 #include "qgsimagecache.h"
24 #include "qgsimageoperation.h"
25 #include "qgsrendercontext.h"
26 #include "qgslogger.h"
27 #include "qgssvgcache.h"
28 #include "qgsunittypes.h"
29 #include "qgssymbol.h"
30 #include "qgsfillsymbol.h"
31 #include "qgsfontmanager.h"
32 
33 #include <QPainter>
34 #include <QSvgRenderer>
35 #include <QFileInfo>
36 #include <QDir>
37 #include <QDomDocument>
38 #include <QDomElement>
39 #include <QUrlQuery>
40 
41 #include <cmath>
42 
43 Q_GUI_EXPORT extern int qt_defaultDpiX();
44 Q_GUI_EXPORT extern int qt_defaultDpiY();
45 
46 static constexpr int MAX_FONT_CHARACTER_SIZE_IN_PIXELS = 500;
47 
48 static void _fixQPictureDPI( QPainter *p )
49 {
50  // QPicture makes an assumption that we drawing to it with system DPI.
51  // Then when being drawn, it scales the painter. The following call
52  // negates the effect. There is no way of setting QPicture's DPI.
53  // See QTBUG-20361
54  p->scale( static_cast< double >( qt_defaultDpiX() ) / p->device()->logicalDpiX(),
55  static_cast< double >( qt_defaultDpiY() ) / p->device()->logicalDpiY() );
56 }
57 
58 
60 
61 
62 //
63 // QgsSimpleMarkerSymbolLayerBase
64 //
65 
67 {
68  QList< Qgis::MarkerShape > shapes;
106 
107  return shapes;
108 }
109 
111  : mShape( shape )
112 {
113  mSize = size;
114  mAngle = angle;
115  mOffset = QPointF( 0, 0 );
119 }
120 
122 
124 {
125  switch ( shape )
126  {
157  return true;
158 
166  return false;
167  }
168  return true;
169 }
170 
172 {
173  const bool hasDataDefinedRotation = context.renderHints() & Qgis::SymbolRenderHint::DynamicRotation
175  const bool hasDataDefinedSize = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertySize );
176 
177  // use either QPolygonF or QPainterPath for drawing
178  if ( !prepareMarkerShape( mShape ) ) // drawing as a polygon
179  {
180  prepareMarkerPath( mShape ); // drawing as a painter path
181  }
182 
183  QTransform transform;
184 
185  // scale the shape (if the size is not going to be modified)
186  if ( !hasDataDefinedSize )
187  {
188  double scaledSize = context.renderContext().convertToPainterUnits( mSize, mSizeUnit, mSizeMapUnitScale );
190  {
191  // rendering for symbol previews -- an size in meters in map units can't be calculated, so treat the size as millimeters
192  // and clamp it to a reasonable range. It's the best we can do in this situation!
193  scaledSize = std::min( std::max( context.renderContext().convertToPainterUnits( mSize, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
194  }
195 
196  const double half = scaledSize / 2.0;
197  transform.scale( half, half );
198  }
199 
200  // rotate if the rotation is not going to be changed during the rendering
201  if ( !hasDataDefinedRotation && !qgsDoubleNear( mAngle, 0.0 ) )
202  {
203  transform.rotate( mAngle );
204  }
205 
206  if ( !mPolygon.isEmpty() )
207  mPolygon = transform.map( mPolygon );
208  else
209  mPath = transform.map( mPath );
210 
212 }
213 
215 {
216  Q_UNUSED( context )
217 }
218 
220 {
221  //making changes here? Don't forget to also update ::bounds if the changes affect the bounding box
222  //of the rendered point!
223 
224  QPainter *p = context.renderContext().painter();
225  if ( !p )
226  {
227  return;
228  }
229 
230  bool hasDataDefinedSize = false;
231  const double scaledSize = calculateSize( context, hasDataDefinedSize );
232 
233  bool hasDataDefinedRotation = false;
234  QPointF offset;
235  double angle = 0;
236  calculateOffsetAndRotation( context, scaledSize, hasDataDefinedRotation, offset, angle );
237 
238  //data defined shape?
239  bool createdNewPath = false;
240  bool ok = true;
241  Qgis::MarkerShape symbol = mShape;
243  {
244  context.setOriginalValueVariable( encodeShape( symbol ) );
246  if ( !QgsVariantUtils::isNull( exprVal ) )
247  {
248  const Qgis::MarkerShape decoded = decodeShape( exprVal.toString(), &ok );
249  if ( ok )
250  {
251  symbol = decoded;
252 
253  if ( !prepareMarkerShape( symbol ) ) // drawing as a polygon
254  {
255  prepareMarkerPath( symbol ); // drawing as a painter path
256  }
257  createdNewPath = true;
258  }
259  }
260  else
261  {
262  symbol = mShape;
263  }
264  }
265 
266  QTransform transform;
267 
268  // move to the desired position
269  transform.translate( point.x() + offset.x(), point.y() + offset.y() );
270 
271  // resize if necessary
272  if ( hasDataDefinedSize || createdNewPath )
273  {
274  double s = context.renderContext().convertToPainterUnits( scaledSize, mSizeUnit, mSizeMapUnitScale );
276  {
277  // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
278  // and clamp it to a reasonable range. It's the best we can do in this situation!
279  s = std::min( std::max( context.renderContext().convertToPainterUnits( mSize, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
280  }
281  const double half = s / 2.0;
282  transform.scale( half, half );
283  }
284 
285  if ( !qgsDoubleNear( angle, 0.0 ) && ( hasDataDefinedRotation || createdNewPath ) )
286  {
287  transform.rotate( angle );
288  }
289 
290  //need to pass: symbol, polygon, path
291 
292  QPolygonF polygon;
293  QPainterPath path;
294  if ( !mPolygon.isEmpty() )
295  {
296  polygon = transform.map( mPolygon );
297  }
298  else
299  {
300  path = transform.map( mPath );
301  }
302  draw( context, symbol, polygon, path );
303 }
304 
306 {
307  bool hasDataDefinedSize = false;
308  double scaledSize = calculateSize( context, hasDataDefinedSize );
309 
310  bool hasDataDefinedRotation = false;
311  QPointF offset;
312  double angle = 0;
313  calculateOffsetAndRotation( context, scaledSize, hasDataDefinedRotation, offset, angle );
314 
315  scaledSize = context.renderContext().convertToPainterUnits( scaledSize, mSizeUnit, mSizeMapUnitScale );
316 
317  QTransform transform;
318 
319  // move to the desired position
320  transform.translate( point.x() + offset.x(), point.y() + offset.y() );
321 
322  if ( !qgsDoubleNear( angle, 0.0 ) )
323  transform.rotate( angle );
324 
325  return transform.mapRect( QRectF( -scaledSize / 2.0,
326  -scaledSize / 2.0,
327  scaledSize,
328  scaledSize ) );
329 }
330 
332 {
333  if ( ok )
334  *ok = true;
335  const QString cleaned = name.toLower().trimmed();
336 
337  if ( cleaned == QLatin1String( "square" ) || cleaned == QLatin1String( "rectangle" ) )
339  else if ( cleaned == QLatin1String( "trapezoid" ) )
341  else if ( cleaned == QLatin1String( "parallelogram_right" ) )
343  else if ( cleaned == QLatin1String( "parallelogram_left" ) )
345  else if ( cleaned == QLatin1String( "square_with_corners" ) )
347  else if ( cleaned == QLatin1String( "rounded_square" ) )
349  else if ( cleaned == QLatin1String( "diamond" ) )
351  else if ( cleaned == QLatin1String( "shield" ) )
353  else if ( cleaned == QLatin1String( "pentagon" ) )
355  else if ( cleaned == QLatin1String( "hexagon" ) )
357  else if ( cleaned == QLatin1String( "octagon" ) )
359  else if ( cleaned == QLatin1String( "decagon" ) )
361  else if ( cleaned == QLatin1String( "triangle" ) )
363  else if ( cleaned == QLatin1String( "equilateral_triangle" ) )
365  else if ( cleaned == QLatin1String( "star_diamond" ) )
367  else if ( cleaned == QLatin1String( "star" ) || cleaned == QLatin1String( "regular_star" ) )
369  else if ( cleaned == QLatin1String( "heart" ) )
371  else if ( cleaned == QLatin1String( "arrow" ) )
373  else if ( cleaned == QLatin1String( "circle" ) )
375  else if ( cleaned == QLatin1String( "cross" ) )
377  else if ( cleaned == QLatin1String( "cross_fill" ) )
379  else if ( cleaned == QLatin1String( "cross2" ) || cleaned == QLatin1String( "x" ) )
381  else if ( cleaned == QLatin1String( "line" ) )
383  else if ( cleaned == QLatin1String( "arrowhead" ) )
385  else if ( cleaned == QLatin1String( "filled_arrowhead" ) )
387  else if ( cleaned == QLatin1String( "semi_circle" ) )
389  else if ( cleaned == QLatin1String( "third_circle" ) )
391  else if ( cleaned == QLatin1String( "quarter_circle" ) )
393  else if ( cleaned == QLatin1String( "quarter_square" ) )
395  else if ( cleaned == QLatin1String( "half_square" ) )
397  else if ( cleaned == QLatin1String( "diagonal_half_square" ) )
399  else if ( cleaned == QLatin1String( "right_half_triangle" ) )
401  else if ( cleaned == QLatin1String( "left_half_triangle" ) )
403  else if ( cleaned == QLatin1String( "asterisk_fill" ) )
405  else if ( cleaned == QLatin1String( "half_arc" ) )
407  else if ( cleaned == QLatin1String( "third_arc" ) )
409  else if ( cleaned == QLatin1String( "quarter_arc" ) )
411 
412  if ( ok )
413  *ok = false;
415 }
416 
418 {
419  switch ( shape )
420  {
422  return QStringLiteral( "square" );
424  return QStringLiteral( "quarter_square" );
426  return QStringLiteral( "half_square" );
428  return QStringLiteral( "diagonal_half_square" );
430  return QStringLiteral( "parallelogram_right" );
432  return QStringLiteral( "parallelogram_left" );
434  return QStringLiteral( "trapezoid" );
436  return QStringLiteral( "shield" );
438  return QStringLiteral( "diamond" );
440  return QStringLiteral( "pentagon" );
442  return QStringLiteral( "hexagon" );
444  return QStringLiteral( "octagon" );
446  return QStringLiteral( "decagon" );
448  return QStringLiteral( "square_with_corners" );
450  return QStringLiteral( "rounded_square" );
452  return QStringLiteral( "triangle" );
454  return QStringLiteral( "equilateral_triangle" );
456  return QStringLiteral( "left_half_triangle" );
458  return QStringLiteral( "right_half_triangle" );
460  return QStringLiteral( "star_diamond" );
462  return QStringLiteral( "star" );
464  return QStringLiteral( "heart" );
466  return QStringLiteral( "arrow" );
468  return QStringLiteral( "filled_arrowhead" );
470  return QStringLiteral( "cross_fill" );
472  return QStringLiteral( "circle" );
474  return QStringLiteral( "cross" );
476  return QStringLiteral( "cross2" );
478  return QStringLiteral( "line" );
480  return QStringLiteral( "arrowhead" );
482  return QStringLiteral( "semi_circle" );
484  return QStringLiteral( "third_circle" );
486  return QStringLiteral( "quarter_circle" );
488  return QStringLiteral( "asterisk_fill" );
490  return QStringLiteral( "half_arc" );
492  return QStringLiteral( "third_arc" );
494  return QStringLiteral( "quarter_arc" );
495  }
496  return QString();
497 }
498 
500 {
501  return shapeToPolygon( shape, mPolygon );
502 }
503 
505 {
506  polygon.clear();
507 
508  switch ( shape )
509  {
511  polygon = QPolygonF( QRectF( QPointF( -1, -1 ), QPointF( 1, 1 ) ) );
512  return true;
513 
515  {
516  static constexpr double VERTEX_OFFSET_FROM_ORIGIN = 0.6072;
517 
518  polygon << QPointF( - VERTEX_OFFSET_FROM_ORIGIN, 1 )
519  << QPointF( VERTEX_OFFSET_FROM_ORIGIN, 1 )
520  << QPointF( 1, VERTEX_OFFSET_FROM_ORIGIN )
521  << QPointF( 1, -VERTEX_OFFSET_FROM_ORIGIN )
522  << QPointF( VERTEX_OFFSET_FROM_ORIGIN, -1 )
523  << QPointF( -VERTEX_OFFSET_FROM_ORIGIN, -1 )
524  << QPointF( -1, -VERTEX_OFFSET_FROM_ORIGIN )
525  << QPointF( -1, VERTEX_OFFSET_FROM_ORIGIN )
526  << QPointF( -VERTEX_OFFSET_FROM_ORIGIN, 1 );
527  return true;
528  }
529 
531  polygon = QPolygonF( QRectF( QPointF( -1, -1 ), QPointF( 0, 0 ) ) );
532  return true;
533 
535  polygon = QPolygonF( QRectF( QPointF( -1, -1 ), QPointF( 0, 1 ) ) );
536  return true;
537 
539  polygon << QPointF( -1, -1 ) << QPointF( 1, 1 ) << QPointF( -1, 1 ) << QPointF( -1, -1 );
540  return true;
541 
543  polygon << QPointF( 0.5, -0.5 )
544  << QPointF( 1, 0.5 )
545  << QPointF( -1, 0.5 )
546  << QPointF( -0.5, -0.5 )
547  << QPointF( 0.5, -0.5 );
548  return true;
549 
551  polygon << QPointF( 0.5, 0.5 )
552  << QPointF( 1, -0.5 )
553  << QPointF( -0.5, -0.5 )
554  << QPointF( -1, 0.5 )
555  << QPointF( 0.5, 0.5 );
556  return true;
557 
559  polygon << QPointF( 1, 0.5 )
560  << QPointF( 0.5, -0.5 )
561  << QPointF( -1, -0.5 )
562  << QPointF( -0.5, 0.5 )
563  << QPointF( 1, 0.5 );
564  return true;
565 
567  polygon << QPointF( -1, 0 ) << QPointF( 0, 1 )
568  << QPointF( 1, 0 ) << QPointF( 0, -1 ) << QPointF( -1, 0 );
569  return true;
570 
572  polygon << QPointF( 1, 0.5 )
573  << QPointF( 1, -1 )
574  << QPointF( -1, -1 )
575  << QPointF( -1, 0.5 )
576  << QPointF( 0, 1 )
577  << QPointF( 1, 0.5 );
578  return true;
579 
581  /* angular-representation of hardcoded values used
582  polygon << QPointF( std::sin( DEG2RAD( 288.0 ) ), - std::cos( DEG2RAD( 288.0 ) ) )
583  << QPointF( std::sin( DEG2RAD( 216.0 ) ), - std::cos( DEG2RAD( 216.0 ) ) )
584  << QPointF( std::sin( DEG2RAD( 144.0 ) ), - std::cos( DEG2RAD( 144.0 ) ) )
585  << QPointF( std::sin( DEG2RAD( 72.0 ) ), - std::cos( DEG2RAD( 72.0 ) ) )
586  << QPointF( 0, -1 ); */
587  polygon << QPointF( -0.9511, -0.3090 )
588  << QPointF( -0.5878, 0.8090 )
589  << QPointF( 0.5878, 0.8090 )
590  << QPointF( 0.9511, -0.3090 )
591  << QPointF( 0, -1 )
592  << QPointF( -0.9511, -0.3090 );
593  return true;
594 
596  /* angular-representation of hardcoded values used
597  polygon << QPointF( std::sin( DEG2RAD( 300.0 ) ), - std::cos( DEG2RAD( 300.0 ) ) )
598  << QPointF( std::sin( DEG2RAD( 240.0 ) ), - std::cos( DEG2RAD( 240.0 ) ) )
599  << QPointF( std::sin( DEG2RAD( 180.0 ) ), - std::cos( DEG2RAD( 180.0 ) ) )
600  << QPointF( std::sin( DEG2RAD( 120.0 ) ), - std::cos( DEG2RAD( 120.0 ) ) )
601  << QPointF( std::sin( DEG2RAD( 60.0 ) ), - std::cos( DEG2RAD( 60.0 ) ) )
602  << QPointF( 0, -1 ); */
603  polygon << QPointF( -0.8660, -0.5 )
604  << QPointF( -0.8660, 0.5 )
605  << QPointF( 0, 1 )
606  << QPointF( 0.8660, 0.5 )
607  << QPointF( 0.8660, -0.5 )
608  << QPointF( 0, -1 )
609  << QPointF( -0.8660, -0.5 );
610  return true;
611 
613  {
614  static constexpr double VERTEX_OFFSET_FROM_ORIGIN = 1.0 / ( 1 + M_SQRT2 );
615 
616  polygon << QPointF( - VERTEX_OFFSET_FROM_ORIGIN, 1 )
617  << QPointF( VERTEX_OFFSET_FROM_ORIGIN, 1 )
618  << QPointF( 1, VERTEX_OFFSET_FROM_ORIGIN )
619  << QPointF( 1, -VERTEX_OFFSET_FROM_ORIGIN )
620  << QPointF( VERTEX_OFFSET_FROM_ORIGIN, -1 )
621  << QPointF( -VERTEX_OFFSET_FROM_ORIGIN, -1 )
622  << QPointF( -1, -VERTEX_OFFSET_FROM_ORIGIN )
623  << QPointF( -1, VERTEX_OFFSET_FROM_ORIGIN )
624  << QPointF( -VERTEX_OFFSET_FROM_ORIGIN, 1 );
625  return true;
626  }
627 
629  {
630 
631  polygon << QPointF( 0.587785252, 0.809016994 )
632  << QPointF( 0.951056516, 0.309016994 )
633  << QPointF( 0.951056516, -0.309016994 )
634  << QPointF( 0.587785252, -0.809016994 )
635  << QPointF( 0, -1 )
636  << QPointF( -0.587785252, -0.809016994 )
637  << QPointF( -0.951056516, -0.309016994 )
638  << QPointF( -0.951056516, 0.309016994 )
639  << QPointF( -0.587785252, 0.809016994 )
640  << QPointF( 0, 1 )
641  << QPointF( 0.587785252, 0.809016994 );
642  return true;
643  }
644 
646  polygon << QPointF( -1, 1 ) << QPointF( 1, 1 ) << QPointF( 0, -1 ) << QPointF( -1, 1 );
647  return true;
648 
650  /* angular-representation of hardcoded values used
651  polygon << QPointF( std::sin( DEG2RAD( 240.0 ) ), - std::cos( DEG2RAD( 240.0 ) ) )
652  << QPointF( std::sin( DEG2RAD( 120.0 ) ), - std::cos( DEG2RAD( 120.0 ) ) )
653  << QPointF( 0, -1 ); */
654  polygon << QPointF( -0.8660, 0.5 )
655  << QPointF( 0.8660, 0.5 )
656  << QPointF( 0, -1 )
657  << QPointF( -0.8660, 0.5 );
658  return true;
659 
661  polygon << QPointF( 0, 1 ) << QPointF( 1, 1 ) << QPointF( 0, -1 ) << QPointF( 0, 1 );
662  return true;
663 
665  polygon << QPointF( -1, 1 ) << QPointF( 0, 1 ) << QPointF( 0, -1 ) << QPointF( -1, 1 );
666  return true;
667 
669  {
670  const double inner_r = std::cos( DEG2RAD( 72.0 ) ) / std::cos( DEG2RAD( 36.0 ) );
671 
672  polygon << QPointF( inner_r * std::sin( DEG2RAD( 315.0 ) ), - inner_r * std::cos( DEG2RAD( 315.0 ) ) )
673  << QPointF( std::sin( DEG2RAD( 270 ) ), - std::cos( DEG2RAD( 270 ) ) )
674  << QPointF( inner_r * std::sin( DEG2RAD( 225.0 ) ), - inner_r * std::cos( DEG2RAD( 225.0 ) ) )
675  << QPointF( std::sin( DEG2RAD( 180 ) ), - std::cos( DEG2RAD( 180 ) ) )
676  << QPointF( inner_r * std::sin( DEG2RAD( 135.0 ) ), - inner_r * std::cos( DEG2RAD( 135.0 ) ) )
677  << QPointF( std::sin( DEG2RAD( 90 ) ), - std::cos( DEG2RAD( 90 ) ) )
678  << QPointF( inner_r * std::sin( DEG2RAD( 45.0 ) ), - inner_r * std::cos( DEG2RAD( 45.0 ) ) )
679  << QPointF( std::sin( DEG2RAD( 0 ) ), - std::cos( DEG2RAD( 0 ) ) );
680  return true;
681  }
682 
684  {
685  const double inner_r = std::cos( DEG2RAD( 72.0 ) ) / std::cos( DEG2RAD( 36.0 ) );
686 
687  polygon << QPointF( inner_r * std::sin( DEG2RAD( 324.0 ) ), - inner_r * std::cos( DEG2RAD( 324.0 ) ) ) // 324
688  << QPointF( std::sin( DEG2RAD( 288.0 ) ), - std::cos( DEG2RAD( 288 ) ) ) // 288
689  << QPointF( inner_r * std::sin( DEG2RAD( 252.0 ) ), - inner_r * std::cos( DEG2RAD( 252.0 ) ) ) // 252
690  << QPointF( std::sin( DEG2RAD( 216.0 ) ), - std::cos( DEG2RAD( 216.0 ) ) ) // 216
691  << QPointF( 0, inner_r ) // 180
692  << QPointF( std::sin( DEG2RAD( 144.0 ) ), - std::cos( DEG2RAD( 144.0 ) ) ) // 144
693  << QPointF( inner_r * std::sin( DEG2RAD( 108.0 ) ), - inner_r * std::cos( DEG2RAD( 108.0 ) ) ) // 108
694  << QPointF( std::sin( DEG2RAD( 72.0 ) ), - std::cos( DEG2RAD( 72.0 ) ) ) // 72
695  << QPointF( inner_r * std::sin( DEG2RAD( 36.0 ) ), - inner_r * std::cos( DEG2RAD( 36.0 ) ) ) // 36
696  << QPointF( 0, -1 )
697  << QPointF( inner_r * std::sin( DEG2RAD( 324.0 ) ), - inner_r * std::cos( DEG2RAD( 324.0 ) ) ); // 324; // 0
698  return true;
699  }
700 
702  polygon << QPointF( 0, -1 )
703  << QPointF( 0.5, -0.5 )
704  << QPointF( 0.25, -0.5 )
705  << QPointF( 0.25, 1 )
706  << QPointF( -0.25, 1 )
707  << QPointF( -0.25, -0.5 )
708  << QPointF( -0.5, -0.5 )
709  << QPointF( 0, -1 );
710  return true;
711 
713  polygon << QPointF( 0, 0 ) << QPointF( -1, 1 ) << QPointF( -1, -1 ) << QPointF( 0, 0 );
714  return true;
715 
717  polygon << QPointF( -1, -0.2 )
718  << QPointF( -1, -0.2 )
719  << QPointF( -1, 0.2 )
720  << QPointF( -0.2, 0.2 )
721  << QPointF( -0.2, 1 )
722  << QPointF( 0.2, 1 )
723  << QPointF( 0.2, 0.2 )
724  << QPointF( 1, 0.2 )
725  << QPointF( 1, -0.2 )
726  << QPointF( 0.2, -0.2 )
727  << QPointF( 0.2, -1 )
728  << QPointF( -0.2, -1 )
729  << QPointF( -0.2, -0.2 )
730  << QPointF( -1, -0.2 );
731  return true;
732 
734  {
735  static constexpr double THICKNESS = 0.3;
736  static constexpr double HALF_THICKNESS = THICKNESS / 2.0;
737  static constexpr double INTERSECTION_POINT = THICKNESS / M_SQRT2;
738  static constexpr double DIAGONAL1 = M_SQRT1_2 - INTERSECTION_POINT * 0.5;
739  static constexpr double DIAGONAL2 = M_SQRT1_2 + INTERSECTION_POINT * 0.5;
740 
741  polygon << QPointF( -HALF_THICKNESS, -1 )
742  << QPointF( HALF_THICKNESS, -1 )
743  << QPointF( HALF_THICKNESS, -HALF_THICKNESS - INTERSECTION_POINT )
744  << QPointF( DIAGONAL1, -DIAGONAL2 )
745  << QPointF( DIAGONAL2, -DIAGONAL1 )
746  << QPointF( HALF_THICKNESS + INTERSECTION_POINT, -HALF_THICKNESS )
747  << QPointF( 1, -HALF_THICKNESS )
748  << QPointF( 1, HALF_THICKNESS )
749  << QPointF( HALF_THICKNESS + INTERSECTION_POINT, HALF_THICKNESS )
750  << QPointF( DIAGONAL2, DIAGONAL1 )
751  << QPointF( DIAGONAL1, DIAGONAL2 )
752  << QPointF( HALF_THICKNESS, HALF_THICKNESS + INTERSECTION_POINT )
753  << QPointF( HALF_THICKNESS, 1 )
754  << QPointF( -HALF_THICKNESS, 1 )
755  << QPointF( -HALF_THICKNESS, HALF_THICKNESS + INTERSECTION_POINT )
756  << QPointF( -DIAGONAL1, DIAGONAL2 )
757  << QPointF( -DIAGONAL2, DIAGONAL1 )
758  << QPointF( -HALF_THICKNESS - INTERSECTION_POINT, HALF_THICKNESS )
759  << QPointF( -1, HALF_THICKNESS )
760  << QPointF( -1, -HALF_THICKNESS )
761  << QPointF( -HALF_THICKNESS - INTERSECTION_POINT, -HALF_THICKNESS )
762  << QPointF( -DIAGONAL2, -DIAGONAL1 )
763  << QPointF( -DIAGONAL1, -DIAGONAL2 )
764  << QPointF( -HALF_THICKNESS, -HALF_THICKNESS - INTERSECTION_POINT )
765  << QPointF( -HALF_THICKNESS, -1 );
766  return true;
767  }
768 
782  return false;
783  }
784 
785  return false;
786 }
787 
789 {
790  mPath = QPainterPath();
791 
792  switch ( symbol )
793  {
795 
796  mPath.addEllipse( QRectF( -1, -1, 2, 2 ) ); // x,y,w,h
797  return true;
798 
800  mPath.moveTo( -1, -1 );
801  mPath.addRoundedRect( -1, -1, 2, 2, 0.25, 0.25 );
802  return true;
803 
805  mPath.arcTo( -1, -1, 2, 2, 0, 180 );
806  mPath.lineTo( 0, 0 );
807  return true;
808 
810  mPath.arcTo( -1, -1, 2, 2, 90, 120 );
811  mPath.lineTo( 0, 0 );
812  return true;
813 
815  mPath.arcTo( -1, -1, 2, 2, 90, 90 );
816  mPath.lineTo( 0, 0 );
817  return true;
818 
820  mPath.moveTo( 1, 0 );
821  mPath.arcTo( -1, -1, 2, 2, 0, 180 );
822  return true;
823 
825  mPath.moveTo( 0, -1 );
826  mPath.arcTo( -1, -1, 2, 2, 90, 120 );
827  return true;
828 
830  mPath.moveTo( 0, -1 );
831  mPath.arcTo( -1, -1, 2, 2, 90, 90 );
832  return true;
833 
835  mPath.moveTo( -1, 0 );
836  mPath.lineTo( 1, 0 ); // horizontal
837  mPath.moveTo( 0, -1 );
838  mPath.lineTo( 0, 1 ); // vertical
839  return true;
840 
842  mPath.moveTo( -1, -1 );
843  mPath.lineTo( 1, 1 );
844  mPath.moveTo( 1, -1 );
845  mPath.lineTo( -1, 1 );
846  return true;
847 
849  mPath.moveTo( 0, -1 );
850  mPath.lineTo( 0, 1 ); // vertical line
851  return true;
852 
854  mPath.moveTo( -1, -1 );
855  mPath.lineTo( 0, 0 );
856  mPath.lineTo( -1, 1 );
857  return true;
858 
860  mPath.moveTo( 0, 0.75 );
861  mPath.arcTo( 0, -1, 1, 1, -45, 210 );
862  mPath.arcTo( -1, -1, 1, 1, 15, 210 );
863  mPath.lineTo( 0, 0.75 );
864  return true;
865 
890  return false;
891  }
892  return false;
893 }
894 
895 double QgsSimpleMarkerSymbolLayerBase::calculateSize( QgsSymbolRenderContext &context, bool &hasDataDefinedSize ) const
896 {
897  double scaledSize = mSize;
898 
900  bool ok = true;
901  if ( hasDataDefinedSize )
902  {
903  context.setOriginalValueVariable( mSize );
905  mSize, &ok );
906  }
907 
908  if ( hasDataDefinedSize && ok )
909  {
910  switch ( mScaleMethod )
911  {
913  scaledSize = std::sqrt( scaledSize );
914  break;
916  break;
917  }
918  }
919 
920  return scaledSize;
921 }
922 
923 void QgsSimpleMarkerSymbolLayerBase::calculateOffsetAndRotation( QgsSymbolRenderContext &context, double scaledSize, bool &hasDataDefinedRotation, QPointF &offset, double &angle ) const
924 {
925  //offset
926  double offsetX = 0;
927  double offsetY = 0;
928  markerOffset( context, scaledSize, scaledSize, offsetX, offsetY );
929  offset = QPointF( offsetX, offsetY );
930 
931  hasDataDefinedRotation = false;
932  //angle
933  bool ok = true;
934  angle = mAngle + mLineAngle;
936  {
937  context.setOriginalValueVariable( angle );
939 
940  // If the expression evaluation was not successful, fallback to static value
941  if ( !ok )
942  angle = mAngle + mLineAngle;
943 
944  hasDataDefinedRotation = true;
945  }
946 
947  hasDataDefinedRotation = context.renderHints() & Qgis::SymbolRenderHint::DynamicRotation || hasDataDefinedRotation;
948 
949  if ( hasDataDefinedRotation )
950  {
951  // For non-point markers, "dataDefinedRotation" means following the
952  // shape (shape-data defined). For them, "field-data defined" does
953  // not work at all. TODO: if "field-data defined" ever gets implemented
954  // we'll need a way to distinguish here between the two, possibly
955  // using another flag in renderHints()
956  const QgsFeature *f = context.feature();
957  if ( f )
958  {
959  if ( f->hasGeometry() && f->geometry().type() == QgsWkbTypes::PointGeometry )
960  {
961  const QgsMapToPixel &m2p = context.renderContext().mapToPixel();
962  angle += m2p.mapRotation();
963  }
964  }
965  }
966 
967  if ( angle )
969 }
970 
971 
972 //
973 // QgsSimpleMarkerSymbolLayer
974 //
975 
976 QgsSimpleMarkerSymbolLayer::QgsSimpleMarkerSymbolLayer( Qgis::MarkerShape shape, double size, double angle, Qgis::ScaleMethod scaleMethod, const QColor &color, const QColor &strokeColor, Qt::PenJoinStyle penJoinStyle )
977  : QgsSimpleMarkerSymbolLayerBase( shape, size, angle, scaleMethod )
978  , mStrokeColor( strokeColor )
979  , mPenJoinStyle( penJoinStyle )
980 {
981  mColor = color;
982 }
983 
985 
987 {
991  Qt::PenJoinStyle penJoinStyle = DEFAULT_SIMPLEMARKER_JOINSTYLE;
995 
996  if ( props.contains( QStringLiteral( "name" ) ) )
997  {
998  shape = decodeShape( props[QStringLiteral( "name" )].toString() );
999  }
1000  if ( props.contains( QStringLiteral( "color" ) ) )
1001  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
1002  if ( props.contains( QStringLiteral( "color_border" ) ) )
1003  {
1004  //pre 2.5 projects use "color_border"
1005  strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color_border" )].toString() );
1006  }
1007  else if ( props.contains( QStringLiteral( "outline_color" ) ) )
1008  {
1009  strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline_color" )].toString() );
1010  }
1011  else if ( props.contains( QStringLiteral( "line_color" ) ) )
1012  {
1013  strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "line_color" )].toString() );
1014  }
1015  if ( props.contains( QStringLiteral( "joinstyle" ) ) )
1016  {
1017  penJoinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() );
1018  }
1019  if ( props.contains( QStringLiteral( "size" ) ) )
1020  size = props[QStringLiteral( "size" )].toDouble();
1021  if ( props.contains( QStringLiteral( "angle" ) ) )
1022  angle = props[QStringLiteral( "angle" )].toDouble();
1023  if ( props.contains( QStringLiteral( "scale_method" ) ) )
1024  scaleMethod = QgsSymbolLayerUtils::decodeScaleMethod( props[QStringLiteral( "scale_method" )].toString() );
1025 
1027  if ( props.contains( QStringLiteral( "offset" ) ) )
1028  m->setOffset( QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() ) );
1029  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
1030  m->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
1031  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1032  m->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1033  if ( props.contains( QStringLiteral( "size_unit" ) ) )
1034  m->setSizeUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "size_unit" )].toString() ) );
1035  if ( props.contains( QStringLiteral( "size_map_unit_scale" ) ) )
1036  m->setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "size_map_unit_scale" )].toString() ) );
1037 
1038  if ( props.contains( QStringLiteral( "outline_style" ) ) )
1039  {
1040  m->setStrokeStyle( QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() ) );
1041  }
1042  else if ( props.contains( QStringLiteral( "line_style" ) ) )
1043  {
1044  m->setStrokeStyle( QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() ) );
1045  }
1046  if ( props.contains( QStringLiteral( "outline_width" ) ) )
1047  {
1048  m->setStrokeWidth( props[QStringLiteral( "outline_width" )].toDouble() );
1049  }
1050  else if ( props.contains( QStringLiteral( "line_width" ) ) )
1051  {
1052  m->setStrokeWidth( props[QStringLiteral( "line_width" )].toDouble() );
1053  }
1054  if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
1055  {
1056  m->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
1057  }
1058  if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
1059  {
1060  m->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
1061  }
1062  if ( props.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
1063  {
1064  m->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
1065  }
1066 
1067  if ( props.contains( QStringLiteral( "horizontal_anchor_point" ) ) )
1068  {
1069  m->setHorizontalAnchorPoint( QgsMarkerSymbolLayer::HorizontalAnchorPoint( props[ QStringLiteral( "horizontal_anchor_point" )].toInt() ) );
1070  }
1071  if ( props.contains( QStringLiteral( "vertical_anchor_point" ) ) )
1072  {
1073  m->setVerticalAnchorPoint( QgsMarkerSymbolLayer::VerticalAnchorPoint( props[ QStringLiteral( "vertical_anchor_point" )].toInt() ) );
1074  }
1075 
1076  if ( props.contains( QStringLiteral( "cap_style" ) ) )
1077  {
1078  m->setPenCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( props[QStringLiteral( "cap_style" )].toString() ) );
1079  }
1080 
1081  m->restoreOldDataDefinedProperties( props );
1082 
1083  return m;
1084 }
1085 
1086 
1088 {
1089  return QStringLiteral( "SimpleMarker" );
1090 }
1091 
1093 {
1095 
1096  QColor brushColor = mColor;
1097  QColor penColor = mStrokeColor;
1098 
1099  brushColor.setAlphaF( mColor.alphaF() * context.opacity() );
1100  penColor.setAlphaF( mStrokeColor.alphaF() * context.opacity() );
1101 
1102  mBrush = QBrush( brushColor );
1103  mPen = QPen( penColor );
1104  mPen.setStyle( mStrokeStyle );
1105  mPen.setCapStyle( mPenCapStyle );
1106  mPen.setJoinStyle( mPenJoinStyle );
1108 
1109  QColor selBrushColor = context.renderContext().selectionColor();
1110  QColor selPenColor = selBrushColor == mColor ? selBrushColor : mStrokeColor;
1111  if ( context.opacity() < 1 && !SELECTION_IS_OPAQUE )
1112  {
1113  selBrushColor.setAlphaF( context.opacity() );
1114  selPenColor.setAlphaF( context.opacity() );
1115  }
1116  mSelBrush = QBrush( selBrushColor );
1117  mSelPen = QPen( selPenColor );
1118  mSelPen.setStyle( mStrokeStyle );
1120 
1122  const bool hasDataDefinedSize = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertySize );
1123 
1124  // use caching only when:
1125  // - size, rotation, shape, color, stroke color is not data-defined
1126  // - drawing to screen (not printer)
1127  mUsingCache = !hasDataDefinedRotation && !hasDataDefinedSize && !context.renderContext().forceVectorOutput()
1131 
1132  if ( mUsingCache )
1133  mCachedOpacity = context.opacity();
1134 
1135  if ( !shapeIsFilled( mShape ) )
1136  {
1137  // some markers can't be drawn as a polygon (circle, cross)
1138  // For these set the selected stroke color to the selected color
1139  mSelPen.setColor( selBrushColor );
1140  }
1141 
1142 
1143  if ( mUsingCache )
1144  {
1145  if ( !prepareCache( context ) )
1146  {
1147  mUsingCache = false;
1148  }
1149  }
1150  else
1151  {
1152  mCache = QImage();
1153  mSelCache = QImage();
1154  }
1155 }
1156 
1157 
1159 {
1160  double scaledSize = context.renderContext().convertToPainterUnits( mSize, mSizeUnit, mSizeMapUnitScale );
1162  {
1163  // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
1164  // and clamp it to a reasonable range. It's the best we can do in this situation!
1165  scaledSize = std::min( std::max( context.renderContext().convertToPainterUnits( mSize, QgsUnitTypes::RenderMillimeters ), 3.0 ), 100.0 );
1166  }
1167 
1168  // take into account angle (which is not data-defined otherwise cache wouldn't be used)
1169  if ( !qgsDoubleNear( mAngle, 0.0 ) )
1170  {
1171  scaledSize = ( std::abs( std::sin( mAngle * M_PI / 180 ) ) + std::abs( std::cos( mAngle * M_PI / 180 ) ) ) * scaledSize;
1172  }
1173  // calculate necessary image size for the cache
1174  const double pw = static_cast< int >( std::round( ( ( qgsDoubleNear( mPen.widthF(), 0.0 ) ? 1 : mPen.widthF() * 4 ) + 1 ) ) ) / 2 * 2; // make even (round up); handle cosmetic pen
1175  const int imageSize = ( static_cast< int >( scaledSize ) + pw ) / 2 * 2 + 1; // make image width, height odd; account for pen width
1176  const double center = imageSize / 2.0;
1177  if ( imageSize > MAXIMUM_CACHE_WIDTH )
1178  {
1179  return false;
1180  }
1181 
1182  mCache = QImage( QSize( imageSize, imageSize ), QImage::Format_ARGB32_Premultiplied );
1183  mCache.fill( 0 );
1184 
1185  const bool needsBrush = shapeIsFilled( mShape );
1186 
1187  QPainter p;
1188  p.begin( &mCache );
1189  p.setRenderHint( QPainter::Antialiasing );
1190  p.setBrush( needsBrush ? mBrush : Qt::NoBrush );
1191  p.setPen( mPen );
1192  p.translate( QPointF( center, center ) );
1193  drawMarker( &p, context );
1194  p.end();
1195 
1196  // Construct the selected version of the Cache
1197 
1198  const QColor selColor = context.renderContext().selectionColor();
1199 
1200  mSelCache = QImage( QSize( imageSize, imageSize ), QImage::Format_ARGB32_Premultiplied );
1201  mSelCache.fill( 0 );
1202 
1203  p.begin( &mSelCache );
1204  p.setRenderHint( QPainter::Antialiasing );
1205  p.setBrush( needsBrush ? mSelBrush : Qt::NoBrush );
1206  p.setPen( mSelPen );
1207  p.translate( QPointF( center, center ) );
1208  drawMarker( &p, context );
1209  p.end();
1210 
1211  // Check that the selected version is different. If not, then re-render,
1212  // filling the background with the selection color and using the normal
1213  // colors for the symbol .. could be ugly!
1214 
1215  if ( mSelCache == mCache )
1216  {
1217  p.begin( &mSelCache );
1218  p.setRenderHint( QPainter::Antialiasing );
1219  p.fillRect( 0, 0, imageSize, imageSize, selColor );
1220  p.setBrush( needsBrush ? mBrush : Qt::NoBrush );
1221  p.setPen( mPen );
1222  p.translate( QPointF( center, center ) );
1223  drawMarker( &p, context );
1224  p.end();
1225  }
1226 
1227  return true;
1228 }
1229 
1230 void QgsSimpleMarkerSymbolLayer::draw( QgsSymbolRenderContext &context, Qgis::MarkerShape shape, const QPolygonF &polygon, const QPainterPath &path )
1231 {
1232  //making changes here? Don't forget to also update ::bounds if the changes affect the bounding box
1233  //of the rendered point!
1234 
1235  QPainter *p = context.renderContext().painter();
1236  if ( !p )
1237  {
1238  return;
1239  }
1240 
1241  QColor brushColor = mColor;
1242  brushColor.setAlphaF( brushColor.alphaF() * context.opacity() );
1243  mBrush.setColor( brushColor );
1244 
1245  QColor penColor = mStrokeColor;
1246  penColor.setAlphaF( penColor.alphaF() * context.opacity() );
1247  mPen.setColor( penColor );
1248 
1249  bool ok = true;
1251  {
1254  if ( ok )
1255  {
1256  c.setAlphaF( c.alphaF() * context.opacity() );
1257  mBrush.setColor( c );
1258  }
1259  }
1261  {
1264  if ( ok )
1265  {
1266  c.setAlphaF( c.alphaF() * context.opacity() );
1267  mPen.setColor( c );
1268  mSelPen.setColor( c );
1269  }
1270  }
1272  {
1275  if ( ok )
1276  {
1279  }
1280  }
1282  {
1285  if ( ok )
1286  {
1289  }
1290  }
1292  {
1294  const QString style = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyJoinStyle, context.renderContext().expressionContext(), QString(), &ok );
1295  if ( ok )
1296  {
1297  mPen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
1298  mSelPen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
1299  }
1300  }
1302  {
1304  const QString style = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyCapStyle, context.renderContext().expressionContext(), QString(), &ok );
1305  if ( ok )
1306  {
1307  mPen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( style ) );
1308  mSelPen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( style ) );
1309  }
1310  }
1311 
1312  if ( shapeIsFilled( shape ) )
1313  {
1314  p->setBrush( context.selected() ? mSelBrush : mBrush );
1315  }
1316  else
1317  {
1318  p->setBrush( Qt::NoBrush );
1319  }
1320  p->setPen( context.selected() ? mSelPen : mPen );
1321 
1322  if ( !polygon.isEmpty() )
1323  p->drawPolygon( polygon );
1324  else
1325  p->drawPath( path );
1326 }
1327 
1329 {
1330  //making changes here? Don't forget to also update ::bounds if the changes affect the bounding box
1331  //of the rendered point!
1332 
1333  QPainter *p = context.renderContext().painter();
1334  if ( !p )
1335  {
1336  return;
1337  }
1338 
1339  if ( mUsingCache && qgsDoubleNear( mCachedOpacity, context.opacity() ) )
1340  {
1341  const QImage &img = context.selected() ? mSelCache : mCache;
1342  const double s = img.width();
1343 
1344  bool hasDataDefinedSize = false;
1345  const double scaledSize = calculateSize( context, hasDataDefinedSize );
1346 
1347  bool hasDataDefinedRotation = false;
1348  QPointF offset;
1349  double angle = 0;
1350  calculateOffsetAndRotation( context, scaledSize, hasDataDefinedRotation, offset, angle );
1351 
1352  p->drawImage( QRectF( point.x() - s / 2.0 + offset.x(),
1353  point.y() - s / 2.0 + offset.y(),
1354  s, s ), img );
1355  }
1356  else
1357  {
1359  }
1360 }
1361 
1363 {
1364  QVariantMap map;
1365  map[QStringLiteral( "name" )] = encodeShape( mShape );
1366  map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
1367  map[QStringLiteral( "outline_color" )] = QgsSymbolLayerUtils::encodeColor( mStrokeColor );
1368  map[QStringLiteral( "size" )] = QString::number( mSize );
1369  map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( mSizeUnit );
1370  map[QStringLiteral( "size_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
1371  map[QStringLiteral( "angle" )] = QString::number( mAngle );
1372  map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1373  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1374  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1375  map[QStringLiteral( "scale_method" )] = QgsSymbolLayerUtils::encodeScaleMethod( mScaleMethod );
1376  map[QStringLiteral( "outline_style" )] = QgsSymbolLayerUtils::encodePenStyle( mStrokeStyle );
1377  map[QStringLiteral( "outline_width" )] = QString::number( mStrokeWidth );
1378  map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( mStrokeWidthUnit );
1379  map[QStringLiteral( "outline_width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale );
1380  map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
1381  map[QStringLiteral( "cap_style" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
1382  map[QStringLiteral( "horizontal_anchor_point" )] = QString::number( mHorizontalAnchorPoint );
1383  map[QStringLiteral( "vertical_anchor_point" )] = QString::number( mVerticalAnchorPoint );
1384  return map;
1385 }
1386 
1388 {
1390  m->setOffset( mOffset );
1391  m->setSizeUnit( mSizeUnit );
1393  m->setOffsetUnit( mOffsetUnit );
1403  copyPaintEffect( m );
1404  return m;
1405 }
1406 
1407 void QgsSimpleMarkerSymbolLayer::writeSldMarker( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
1408 {
1409  // <Graphic>
1410  QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
1411  element.appendChild( graphicElem );
1412 
1414  const double size = QgsSymbolLayerUtils::rescaleUom( mSize, mSizeUnit, props );
1416 
1417  // <Rotation>
1418  QString angleFunc;
1419  bool ok;
1420  const double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
1421  if ( !ok )
1422  {
1423  angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
1424  }
1425  else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
1426  {
1427  angleFunc = QString::number( angle + mAngle );
1428  }
1429  QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
1430 
1431  // <Displacement>
1432  const QPointF offset = QgsSymbolLayerUtils::rescaleUom( mOffset, mOffsetUnit, props );
1434 }
1435 
1436 QString QgsSimpleMarkerSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
1437 {
1438  Q_UNUSED( mmScaleFactor )
1439  Q_UNUSED( mapUnitScaleFactor )
1440 #if 0
1441  QString ogrType = "3"; //default is circle
1442  if ( mName == "square" )
1443  {
1444  ogrType = "5";
1445  }
1446  else if ( mName == "triangle" )
1447  {
1448  ogrType = "7";
1449  }
1450  else if ( mName == "star" )
1451  {
1452  ogrType = "9";
1453  }
1454  else if ( mName == "circle" )
1455  {
1456  ogrType = "3";
1457  }
1458  else if ( mName == "cross" )
1459  {
1460  ogrType = "0";
1461  }
1462  else if ( mName == "x" || mName == "cross2" )
1463  {
1464  ogrType = "1";
1465  }
1466  else if ( mName == "line" )
1467  {
1468  ogrType = "10";
1469  }
1470 
1471  QString ogrString;
1472  ogrString.append( "SYMBOL(" );
1473  ogrString.append( "id:" );
1474  ogrString.append( '\"' );
1475  ogrString.append( "ogr-sym-" );
1476  ogrString.append( ogrType );
1477  ogrString.append( '\"' );
1478  ogrString.append( ",c:" );
1479  ogrString.append( mColor.name() );
1480  ogrString.append( ",o:" );
1481  ogrString.append( mStrokeColor.name() );
1482  ogrString.append( QString( ",s:%1mm" ).arg( mSize ) );
1483  ogrString.append( ')' );
1484  return ogrString;
1485 #endif //0
1486 
1487  QString ogrString;
1488  ogrString.append( "PEN(" );
1489  ogrString.append( "c:" );
1490  ogrString.append( mColor.name() );
1491  ogrString.append( ",w:" );
1492  ogrString.append( QString::number( mSize ) );
1493  ogrString.append( "mm" );
1494  ogrString.append( ")" );
1495  return ogrString;
1496 }
1497 
1499 {
1500  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
1501 
1502  QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
1503  if ( graphicElem.isNull() )
1504  return nullptr;
1505 
1506  QString name = QStringLiteral( "square" );
1507  QColor color, strokeColor;
1508  double strokeWidth, size;
1509  Qt::PenStyle strokeStyle;
1510 
1512  return nullptr;
1513 
1514  double angle = 0.0;
1515  QString angleFunc;
1516  if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
1517  {
1518  bool ok;
1519  const double d = angleFunc.toDouble( &ok );
1520  if ( ok )
1521  angle = d;
1522  }
1523 
1524  QPointF offset;
1526 
1527  const Qgis::MarkerShape shape = decodeShape( name );
1528 
1529  double scaleFactor = 1.0;
1530  const QString uom = element.attribute( QStringLiteral( "uom" ) );
1531  QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
1532  size = size * scaleFactor;
1533  offset.setX( offset.x() * scaleFactor );
1534  offset.setY( offset.y() * scaleFactor );
1535 
1537  m->setOutputUnit( sldUnitSize );
1538  m->setColor( color );
1540  m->setAngle( angle );
1541  m->setOffset( offset );
1544  return m;
1545 }
1546 
1548 {
1549  Q_UNUSED( context )
1550 
1551  if ( mPolygon.count() != 0 )
1552  {
1553  p->drawPolygon( mPolygon );
1554  }
1555  else
1556  {
1557  p->drawPath( mPath );
1558  }
1559 }
1560 
1561 bool QgsSimpleMarkerSymbolLayer::writeDxf( QgsDxfExport &e, double mmMapUnitScaleFactor, const QString &layerName, QgsSymbolRenderContext &context, QPointF shift ) const
1562 {
1563  //data defined size?
1564  double size = mSize;
1565 
1566  const bool hasDataDefinedSize = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertySize );
1567 
1568  //data defined size
1569  bool ok = true;
1570  if ( hasDataDefinedSize )
1571  {
1573 
1574  if ( ok )
1575  {
1576  switch ( mScaleMethod )
1577  {
1579  size = std::sqrt( size );
1580  break;
1582  break;
1583  }
1584  }
1585 
1587  }
1588 
1590  {
1591  size *= mmMapUnitScaleFactor;
1592  }
1593 
1595  {
1597  }
1598  const double halfSize = size / 2.0;
1599 
1600  //strokeWidth
1601  double strokeWidth = mStrokeWidth;
1602 
1604  {
1607  }
1610  {
1612  }
1613 
1614  //color
1615  QColor pc = mPen.color();
1616  QColor bc = mBrush.color();
1618  {
1621  }
1623  {
1626  }
1627 
1628  //offset
1629  double offsetX = 0;
1630  double offsetY = 0;
1631  markerOffset( context, offsetX, offsetY );
1632  offsetX *= context.renderContext().mapToPixel().mapUnitsPerPixel();
1633  offsetY *= context.renderContext().mapToPixel().mapUnitsPerPixel();
1634 
1635 
1636  QPointF off( offsetX, offsetY );
1637 
1638  //angle
1639  double angle = mAngle + mLineAngle;
1641  {
1642  context.setOriginalValueVariable( mAngle );
1644  }
1645 
1648  {
1650  const QString shapeName = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyName, context.renderContext().expressionContext(), QString(), &ok );
1651  if ( ok )
1652  {
1653  shape = decodeShape( shapeName, &ok );
1654  if ( !ok )
1655  shape = mShape;
1656  }
1657  }
1658 
1659  if ( angle )
1660  off = _rotatedOffset( off, angle );
1661 
1663 
1664  QTransform t;
1665  t.translate( shift.x() + off.x(), shift.y() - off.y() );
1666 
1667  if ( !qgsDoubleNear( angle, 0.0 ) )
1668  t.rotate( -angle );
1669 
1670  QPolygonF polygon;
1671  if ( shapeToPolygon( shape, polygon ) )
1672  {
1673  t.scale( halfSize, -halfSize );
1674 
1675  polygon = t.map( polygon );
1676 
1677  QgsPointSequence p;
1678  p.reserve( polygon.size() );
1679  for ( int i = 0; i < polygon.size(); i++ )
1680  {
1681  p << QgsPoint( polygon[i] );
1682  }
1683 
1684  if ( mBrush.style() != Qt::NoBrush )
1685  e.writePolygon( QgsRingSequence() << p, layerName, QStringLiteral( "SOLID" ), bc );
1686  if ( mPen.style() != Qt::NoPen )
1687  e.writePolyline( p, layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1688  }
1689  else if ( shape == Qgis::MarkerShape::Circle )
1690  {
1691  shift += QPointF( off.x(), -off.y() );
1692  if ( mBrush.style() != Qt::NoBrush )
1693  e.writeFilledCircle( layerName, bc, QgsPoint( shift ), halfSize );
1694  if ( mPen.style() != Qt::NoPen )
1695  e.writeCircle( layerName, pc, QgsPoint( shift ), halfSize, QStringLiteral( "CONTINUOUS" ), strokeWidth );
1696  }
1697  else if ( shape == Qgis::MarkerShape::Line )
1698  {
1699  const QPointF pt1 = t.map( QPointF( 0, -halfSize ) );
1700  const QPointF pt2 = t.map( QPointF( 0, halfSize ) );
1701 
1702  if ( mPen.style() != Qt::NoPen )
1703  e.writeLine( QgsPoint( pt1 ), QgsPoint( pt2 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1704  }
1705  else if ( shape == Qgis::MarkerShape::Cross )
1706  {
1707  if ( mPen.style() != Qt::NoPen )
1708  {
1709  const QPointF pt1 = t.map( QPointF( -halfSize, 0 ) );
1710  const QPointF pt2 = t.map( QPointF( halfSize, 0 ) );
1711  const QPointF pt3 = t.map( QPointF( 0, -halfSize ) );
1712  const QPointF pt4 = t.map( QPointF( 0, halfSize ) );
1713 
1714  e.writeLine( QgsPoint( pt1 ), QgsPoint( pt2 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1715  e.writeLine( QgsPoint( pt3 ), QgsPoint( pt4 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1716  }
1717  }
1718  else if ( shape == Qgis::MarkerShape::Cross2 )
1719  {
1720  if ( mPen.style() != Qt::NoPen )
1721  {
1722  const QPointF pt1 = t.map( QPointF( -halfSize, -halfSize ) );
1723  const QPointF pt2 = t.map( QPointF( halfSize, halfSize ) );
1724  const QPointF pt3 = t.map( QPointF( halfSize, -halfSize ) );
1725  const QPointF pt4 = t.map( QPointF( -halfSize, halfSize ) );
1726 
1727  e.writeLine( QgsPoint( pt1 ), QgsPoint( pt2 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1728  e.writeLine( QgsPoint( pt3 ), QgsPoint( pt4 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1729  }
1730  }
1731  else if ( shape == Qgis::MarkerShape::ArrowHead )
1732  {
1733  if ( mPen.style() != Qt::NoPen )
1734  {
1735  const QPointF pt1 = t.map( QPointF( -halfSize, halfSize ) );
1736  const QPointF pt2 = t.map( QPointF( 0, 0 ) );
1737  const QPointF pt3 = t.map( QPointF( -halfSize, -halfSize ) );
1738 
1739  e.writeLine( QgsPoint( pt1 ), QgsPoint( pt2 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1740  e.writeLine( QgsPoint( pt3 ), QgsPoint( pt2 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1741  }
1742  }
1743  else
1744  {
1745  QgsDebugMsg( QStringLiteral( "Unsupported dxf marker name %1" ).arg( encodeShape( shape ) ) );
1746  return false;
1747  }
1748 
1749  return true;
1750 }
1751 
1752 
1754 {
1756  mStrokeWidthUnit = unit;
1757 }
1758 
1760 {
1762  {
1763  return mStrokeWidthUnit;
1764  }
1766 }
1767 
1769 {
1771  mStrokeWidthMapUnitScale = scale;
1772 }
1773 
1775 {
1777  {
1778  return mStrokeWidthMapUnitScale;
1779  }
1780  return QgsMapUnitScale();
1781 }
1782 
1784 {
1788 }
1789 
1791 {
1792  QRectF symbolBounds = QgsSimpleMarkerSymbolLayerBase::bounds( point, context );
1793 
1794  // need to account for stroke width
1795  double penWidth = mStrokeWidth;
1796  bool ok = true;
1798  {
1801  if ( ok )
1802  {
1803  penWidth = strokeWidth;
1804  }
1805  }
1808  {
1811  if ( ok && strokeStyle == QLatin1String( "no" ) )
1812  {
1813  penWidth = 0.0;
1814  }
1815  }
1816  else if ( mStrokeStyle == Qt::NoPen )
1817  penWidth = 0;
1818 
1819  //antialiasing, add 1 pixel
1820  penWidth += 1;
1821 
1822  //extend bounds by pen width / 2.0
1823  symbolBounds.adjust( -penWidth / 2.0, -penWidth / 2.0,
1824  penWidth / 2.0, penWidth / 2.0 );
1825 
1826  return symbolBounds;
1827 }
1828 
1829 void QgsSimpleMarkerSymbolLayer::setColor( const QColor &color )
1830 {
1831  if ( shapeIsFilled( mShape ) )
1832  {
1833  setFillColor( color );
1834  }
1835  else
1836  {
1837  setStrokeColor( color );
1838  }
1839 }
1840 
1842 {
1843  if ( shapeIsFilled( mShape ) )
1844  {
1845  return fillColor();
1846  }
1847  else
1848  {
1849  return strokeColor();
1850  }
1851 }
1852 
1853 
1854 
1855 
1856 //
1857 // QgsFilledMarkerSymbolLayer
1858 //
1859 
1861  : QgsSimpleMarkerSymbolLayerBase( shape, size, angle, scaleMethod )
1862 {
1863  mFill.reset( static_cast<QgsFillSymbol *>( QgsFillSymbol::createSimple( QVariantMap() ) ) );
1864 }
1865 
1867 
1869 {
1870  QString name = DEFAULT_SIMPLEMARKER_NAME;
1874 
1875  if ( props.contains( QStringLiteral( "name" ) ) )
1876  name = props[QStringLiteral( "name" )].toString();
1877  if ( props.contains( QStringLiteral( "size" ) ) )
1878  size = props[QStringLiteral( "size" )].toDouble();
1879  if ( props.contains( QStringLiteral( "angle" ) ) )
1880  angle = props[QStringLiteral( "angle" )].toDouble();
1881  if ( props.contains( QStringLiteral( "scale_method" ) ) )
1882  scaleMethod = QgsSymbolLayerUtils::decodeScaleMethod( props[QStringLiteral( "scale_method" )].toString() );
1883 
1885  if ( props.contains( QStringLiteral( "offset" ) ) )
1886  m->setOffset( QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() ) );
1887  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
1888  m->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
1889  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1890  m->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1891  if ( props.contains( QStringLiteral( "size_unit" ) ) )
1892  m->setSizeUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "size_unit" )].toString() ) );
1893  if ( props.contains( QStringLiteral( "size_map_unit_scale" ) ) )
1894  m->setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "size_map_unit_scale" )].toString() ) );
1895  if ( props.contains( QStringLiteral( "horizontal_anchor_point" ) ) )
1896  {
1897  m->setHorizontalAnchorPoint( QgsMarkerSymbolLayer::HorizontalAnchorPoint( props[ QStringLiteral( "horizontal_anchor_point" )].toInt() ) );
1898  }
1899  if ( props.contains( QStringLiteral( "vertical_anchor_point" ) ) )
1900  {
1901  m->setVerticalAnchorPoint( QgsMarkerSymbolLayer::VerticalAnchorPoint( props[ QStringLiteral( "vertical_anchor_point" )].toInt() ) );
1902  }
1903 
1905 
1906  m->restoreOldDataDefinedProperties( props );
1907 
1908  return m;
1909 }
1910 
1912 {
1913  return QStringLiteral( "FilledMarker" );
1914 }
1915 
1917 {
1918  if ( mFill )
1919  {
1920  mFill->startRender( context.renderContext(), context.fields() );
1921  }
1922 
1924 }
1925 
1927 {
1928  if ( mFill )
1929  {
1930  mFill->stopRender( context.renderContext() );
1931  }
1932 }
1933 
1935 {
1936  QVariantMap map;
1937  map[QStringLiteral( "name" )] = encodeShape( mShape );
1938  map[QStringLiteral( "size" )] = QString::number( mSize );
1939  map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( mSizeUnit );
1940  map[QStringLiteral( "size_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
1941  map[QStringLiteral( "angle" )] = QString::number( mAngle );
1942  map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1943  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1944  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1945  map[QStringLiteral( "scale_method" )] = QgsSymbolLayerUtils::encodeScaleMethod( mScaleMethod );
1946  map[QStringLiteral( "horizontal_anchor_point" )] = QString::number( mHorizontalAnchorPoint );
1947  map[QStringLiteral( "vertical_anchor_point" )] = QString::number( mVerticalAnchorPoint );
1948 
1949  if ( mFill )
1950  {
1951  map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mFill->color() );
1952  }
1953  return map;
1954 }
1955 
1957 {
1959  copyPaintEffect( m );
1961  m->setSubSymbol( mFill->clone() );
1962  return m;
1963 }
1964 
1966 {
1967  return mFill.get();
1968 }
1969 
1971 {
1972  if ( symbol && symbol->type() == Qgis::SymbolType::Fill )
1973  {
1974  mFill.reset( static_cast<QgsFillSymbol *>( symbol ) );
1975  return true;
1976  }
1977  else
1978  {
1979  delete symbol;
1980  return false;
1981  }
1982 }
1983 
1985 {
1986  if ( mFill )
1987  {
1988  return QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFill.get(), context );
1989  }
1990  return 0;
1991 }
1992 
1994 {
1995  QSet<QString> attr = QgsSimpleMarkerSymbolLayerBase::usedAttributes( context );
1996  if ( mFill )
1997  attr.unite( mFill->usedAttributes( context ) );
1998  return attr;
1999 }
2000 
2002 {
2004  return true;
2005  if ( mFill && mFill->hasDataDefinedProperties() )
2006  return true;
2007  return false;
2008 }
2009 
2011 {
2012  mColor = c;
2013  if ( mFill )
2014  mFill->setColor( c );
2015 }
2016 
2018 {
2019  return mFill ? mFill->color() : mColor;
2020 }
2021 
2023 {
2026  || ( mFill && mFill->usesMapUnits() );
2027 }
2028 
2030 {
2032  if ( mFill )
2033  mFill->setOutputUnit( unit );
2034 }
2035 
2036 void QgsFilledMarkerSymbolLayer::draw( QgsSymbolRenderContext &context, Qgis::MarkerShape shape, const QPolygonF &polygon, const QPainterPath &path )
2037 {
2038  //making changes here? Don't forget to also update ::bounds if the changes affect the bounding box
2039  //of the rendered point!
2040 
2041  QPainter *p = context.renderContext().painter();
2042  if ( !p )
2043  {
2044  return;
2045  }
2046 
2047  const double prevOpacity = mFill->opacity();
2048  mFill->setOpacity( mFill->opacity() * context.opacity() );
2049 
2050  if ( shapeIsFilled( shape ) )
2051  {
2052  p->setBrush( Qt::red );
2053  }
2054  else
2055  {
2056  p->setBrush( Qt::NoBrush );
2057  }
2058  p->setPen( Qt::black );
2059 
2060  const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2062 
2063  if ( !polygon.isEmpty() )
2064  {
2065  mFill->renderPolygon( polygon, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
2066  }
2067  else
2068  {
2069  const QPolygonF poly = path.toFillPolygon();
2070  mFill->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, context.selected() );
2071  }
2072 
2074 
2075  mFill->setOpacity( prevOpacity );
2076 }
2077 
2078 
2080 
2081 
2082 QgsSvgMarkerSymbolLayer::QgsSvgMarkerSymbolLayer( const QString &path, double size, double angle, Qgis::ScaleMethod scaleMethod )
2083 {
2084  mSize = size;
2085  mAngle = angle;
2086  mOffset = QPointF( 0, 0 );
2088  mStrokeWidth = 0.2;
2090  mColor = QColor( 35, 35, 35 );
2091  mStrokeColor = QColor( 35, 35, 35 );
2092  setPath( path );
2093 }
2094 
2096 
2098 {
2099  QString name;
2100  double size = DEFAULT_SVGMARKER_SIZE;
2101  double angle = DEFAULT_SVGMARKER_ANGLE;
2103 
2104  if ( props.contains( QStringLiteral( "name" ) ) )
2105  name = props[QStringLiteral( "name" )].toString();
2106  if ( props.contains( QStringLiteral( "size" ) ) )
2107  size = props[QStringLiteral( "size" )].toDouble();
2108  if ( props.contains( QStringLiteral( "angle" ) ) )
2109  angle = props[QStringLiteral( "angle" )].toDouble();
2110  if ( props.contains( QStringLiteral( "scale_method" ) ) )
2111  scaleMethod = QgsSymbolLayerUtils::decodeScaleMethod( props[QStringLiteral( "scale_method" )].toString() );
2112 
2114 
2115  if ( props.contains( QStringLiteral( "size_unit" ) ) )
2116  m->setSizeUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "size_unit" )].toString() ) );
2117  if ( props.contains( QStringLiteral( "size_map_unit_scale" ) ) )
2118  m->setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "size_map_unit_scale" )].toString() ) );
2119  if ( props.contains( QStringLiteral( "fixedAspectRatio" ) ) )
2120  m->setFixedAspectRatio( props[QStringLiteral( "fixedAspectRatio" )].toDouble() );
2121  if ( props.contains( QStringLiteral( "offset" ) ) )
2122  m->setOffset( QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() ) );
2123  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
2124  m->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
2125  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
2126  m->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
2127  if ( props.contains( QStringLiteral( "fill" ) ) )
2128  {
2129  //pre 2.5 projects used "fill"
2130  m->setFillColor( QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "fill" )].toString() ) );
2131  }
2132  else if ( props.contains( QStringLiteral( "color" ) ) )
2133  {
2134  m->setFillColor( QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() ) );
2135  }
2136  if ( props.contains( QStringLiteral( "outline" ) ) )
2137  {
2138  //pre 2.5 projects used "outline"
2139  m->setStrokeColor( QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline" )].toString() ) );
2140  }
2141  else if ( props.contains( QStringLiteral( "outline_color" ) ) )
2142  {
2143  m->setStrokeColor( QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline_color" )].toString() ) );
2144  }
2145  else if ( props.contains( QStringLiteral( "line_color" ) ) )
2146  {
2147  m->setStrokeColor( QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "line_color" )].toString() ) );
2148  }
2149 
2150  if ( props.contains( QStringLiteral( "outline-width" ) ) )
2151  {
2152  //pre 2.5 projects used "outline-width"
2153  m->setStrokeWidth( props[QStringLiteral( "outline-width" )].toDouble() );
2154  }
2155  else if ( props.contains( QStringLiteral( "outline_width" ) ) )
2156  {
2157  m->setStrokeWidth( props[QStringLiteral( "outline_width" )].toDouble() );
2158  }
2159  else if ( props.contains( QStringLiteral( "line_width" ) ) )
2160  {
2161  m->setStrokeWidth( props[QStringLiteral( "line_width" )].toDouble() );
2162  }
2163 
2164  if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
2165  {
2166  m->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
2167  }
2168  else if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
2169  {
2170  m->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
2171  }
2172  if ( props.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2173  m->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2174 
2175  if ( props.contains( QStringLiteral( "horizontal_anchor_point" ) ) )
2176  {
2177  m->setHorizontalAnchorPoint( QgsMarkerSymbolLayer::HorizontalAnchorPoint( props[ QStringLiteral( "horizontal_anchor_point" )].toInt() ) );
2178  }
2179  if ( props.contains( QStringLiteral( "vertical_anchor_point" ) ) )
2180  {
2181  m->setVerticalAnchorPoint( QgsMarkerSymbolLayer::VerticalAnchorPoint( props[ QStringLiteral( "vertical_anchor_point" )].toInt() ) );
2182  }
2183 
2184  m->restoreOldDataDefinedProperties( props );
2185 
2187 
2188  if ( props.contains( QStringLiteral( "parameters" ) ) )
2189  {
2190  const QVariantMap parameters = props[QStringLiteral( "parameters" )].toMap();
2192  }
2193 
2194  return m;
2195 }
2196 
2197 void QgsSvgMarkerSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
2198 {
2199  const QVariantMap::iterator it = properties.find( QStringLiteral( "name" ) );
2200  if ( it != properties.end() )
2201  {
2202  if ( saving )
2203  {
2204  it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
2205  }
2206  else
2207  {
2208  it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
2209  }
2210  }
2211 }
2212 
2213 void QgsSvgMarkerSymbolLayer::setPath( const QString &path )
2214 {
2215  mDefaultAspectRatio = 0;
2216  mHasFillParam = false;
2217  mPath = path;
2218  QColor defaultFillColor, defaultStrokeColor;
2219  double strokeWidth, fillOpacity, strokeOpacity;
2220  bool hasFillOpacityParam = false, hasStrokeParam = false, hasStrokeWidthParam = false, hasStrokeOpacityParam = false;
2221  bool hasDefaultFillColor = false, hasDefaultFillOpacity = false, hasDefaultStrokeColor = false, hasDefaultStrokeWidth = false, hasDefaultStrokeOpacity = false;
2222  QgsApplication::svgCache()->containsParams( path, mHasFillParam, hasDefaultFillColor, defaultFillColor,
2223  hasFillOpacityParam, hasDefaultFillOpacity, fillOpacity,
2224  hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
2225  hasStrokeWidthParam, hasDefaultStrokeWidth, strokeWidth,
2226  hasStrokeOpacityParam, hasDefaultStrokeOpacity, strokeOpacity );
2227 
2228  const double newFillOpacity = hasFillOpacityParam ? fillColor().alphaF() : 1.0;
2229  const double newStrokeOpacity = hasStrokeOpacityParam ? strokeColor().alphaF() : 1.0;
2230 
2231  if ( hasDefaultFillColor )
2232  {
2233  defaultFillColor.setAlphaF( newFillOpacity );
2234  setFillColor( defaultFillColor );
2235  }
2236  if ( hasDefaultFillOpacity )
2237  {
2238  QColor c = fillColor();
2239  c.setAlphaF( fillOpacity );
2240  setFillColor( c );
2241  }
2242  if ( hasDefaultStrokeColor )
2243  {
2244  defaultStrokeColor.setAlphaF( newStrokeOpacity );
2245  setStrokeColor( defaultStrokeColor );
2246  }
2247  if ( hasDefaultStrokeWidth )
2248  {
2250  }
2251  if ( hasDefaultStrokeOpacity )
2252  {
2253  QColor c = strokeColor();
2254  c.setAlphaF( strokeOpacity );
2255  setStrokeColor( c );
2256  }
2257 
2259 }
2260 
2262 {
2263  if ( mDefaultAspectRatio == 0.0 )
2264  {
2265  //size
2266  const double size = mSize;
2267  //assume 88 dpi as standard value
2268  const double widthScaleFactor = 3.465;
2269  const QSizeF svgViewbox = QgsApplication::svgCache()->svgViewboxSize( mPath, size, mColor, mStrokeColor, mStrokeWidth, widthScaleFactor );
2270  // set default aspect ratio
2271  mDefaultAspectRatio = svgViewbox.isValid() ? svgViewbox.height() / svgViewbox.width() : 0.0;
2272  }
2273  return mDefaultAspectRatio;
2274 }
2275 
2277 {
2278  const bool aPreservedAspectRatio = preservedAspectRatio();
2279  if ( aPreservedAspectRatio && !par )
2280  {
2282  }
2283  else if ( !aPreservedAspectRatio && par )
2284  {
2285  mFixedAspectRatio = 0.0;
2286  }
2287  return preservedAspectRatio();
2288 }
2289 
2290 void QgsSvgMarkerSymbolLayer::setParameters( const QMap<QString, QgsProperty> &parameters )
2291 {
2293 }
2294 
2295 
2297 {
2298  return QStringLiteral( "SvgMarker" );
2299 }
2300 
2302 {
2303  QgsMarkerSymbolLayer::startRender( context ); // get anchor point expressions
2304  Q_UNUSED( context )
2305 }
2306 
2308 {
2309  Q_UNUSED( context )
2310 }
2311 
2313 {
2314  QPainter *p = context.renderContext().painter();
2315  if ( !p )
2316  return;
2317 
2318  bool hasDataDefinedSize = false;
2319  const double scaledWidth = calculateSize( context, hasDataDefinedSize );
2320  const double width = context.renderContext().convertToPainterUnits( scaledWidth, mSizeUnit, mSizeMapUnitScale );
2321 
2322  //don't render symbols with a width below one or above 10,000 pixels
2323  if ( static_cast< int >( width ) < 1 || 10000.0 < width )
2324  {
2325  return;
2326  }
2327 
2328  const QgsScopedQPainterState painterState( p );
2329 
2330  bool hasDataDefinedAspectRatio = false;
2331  const double aspectRatio = calculateAspectRatio( context, scaledWidth, hasDataDefinedAspectRatio );
2332  double scaledHeight = scaledWidth * ( !qgsDoubleNear( aspectRatio, 0.0 ) ? aspectRatio : mDefaultAspectRatio );
2333 
2335 
2336  double strokeWidth = mStrokeWidth;
2338  {
2341  }
2343 
2344  QColor fillColor = mColor;
2345  if ( context.selected() && mHasFillParam )
2346  {
2347  fillColor = context.renderContext().selectionColor();
2348  }
2350  {
2353  }
2354 
2355  QColor strokeColor = mStrokeColor;
2357  {
2360  }
2361 
2362  QString path = mPath;
2364  {
2365  context.setOriginalValueVariable( mPath );
2367  context.renderContext().pathResolver() );
2369  {
2370  // adjust height of data defined path
2371  const QSizeF svgViewbox = QgsApplication::svgCache()->svgViewboxSize( path, scaledWidth, fillColor, strokeColor, strokeWidth,
2372  context.renderContext().scaleFactor(), aspectRatio,
2373  ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), evaluatedParameters );
2374  scaledHeight = svgViewbox.isValid() ? scaledWidth * svgViewbox.height() / svgViewbox.width() : scaledWidth;
2375  }
2376  }
2377 
2378  QPointF outputOffset;
2379  double angle = 0.0;
2380  calculateOffsetAndRotation( context, scaledWidth, scaledHeight, outputOffset, angle );
2381 
2382  p->translate( point + outputOffset );
2383 
2384  const bool rotated = !qgsDoubleNear( angle, 0 );
2385  if ( rotated )
2386  p->rotate( angle );
2387 
2388  bool fitsInCache = true;
2389  bool usePict = true;
2390  const bool rasterizeSelected = !mHasFillParam || mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyName );
2391  if ( ( !context.renderContext().forceVectorOutput() && !rotated ) || ( context.selected() && rasterizeSelected ) )
2392  {
2394  context.renderContext().scaleFactor(), fitsInCache, aspectRatio,
2395  ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), evaluatedParameters );
2396  if ( fitsInCache && img.width() > 1 )
2397  {
2398  usePict = false;
2399 
2400  if ( context.selected() )
2401  {
2403  }
2404 
2405  //consider transparency
2406  if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
2407  {
2408  QImage transparentImage = img.copy();
2409  QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
2410  p->drawImage( -transparentImage.width() / 2.0, -transparentImage.height() / 2.0, transparentImage );
2411  }
2412  else
2413  {
2414  p->drawImage( -img.width() / 2.0, -img.height() / 2.0, img );
2415  }
2416  }
2417  }
2418 
2419  if ( usePict || !fitsInCache )
2420  {
2421  p->setOpacity( context.opacity() );
2422  const QPicture pct = QgsApplication::svgCache()->svgAsPicture( path, width, fillColor, strokeColor, strokeWidth,
2423  context.renderContext().scaleFactor(), context.renderContext().forceVectorOutput(), aspectRatio,
2424  ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), evaluatedParameters );
2425  if ( pct.width() > 1 )
2426  {
2427  const QgsScopedQPainterState painterPictureState( p );
2428  _fixQPictureDPI( p );
2429  p->drawPicture( 0, 0, pct );
2430  }
2431  }
2432 
2433  // workaround issue with nested QPictures forgetting antialiasing flag - see https://github.com/qgis/QGIS/issues/22909
2435 }
2436 
2437 double QgsSvgMarkerSymbolLayer::calculateSize( QgsSymbolRenderContext &context, bool &hasDataDefinedSize ) const
2438 {
2439  double scaledSize = mSize;
2441 
2442  bool ok = true;
2443  if ( hasDataDefinedSize )
2444  {
2445  context.setOriginalValueVariable( mSize );
2447  }
2448  else
2449  {
2451  if ( hasDataDefinedSize )
2452  {
2453  context.setOriginalValueVariable( mSize );
2455  }
2456  }
2457 
2458  if ( hasDataDefinedSize && ok )
2459  {
2460  switch ( mScaleMethod )
2461  {
2463  scaledSize = std::sqrt( scaledSize );
2464  break;
2466  break;
2467  }
2468  }
2469 
2470  return scaledSize;
2471 }
2472 
2473 double QgsSvgMarkerSymbolLayer::calculateAspectRatio( QgsSymbolRenderContext &context, double scaledSize, bool &hasDataDefinedAspectRatio ) const
2474 {
2476  if ( !hasDataDefinedAspectRatio )
2477  return mFixedAspectRatio;
2478 
2480  return 0.0;
2481 
2482  double scaledAspectRatio = mDefaultAspectRatio;
2483  if ( mFixedAspectRatio > 0.0 )
2484  scaledAspectRatio = mFixedAspectRatio;
2485 
2486  const double defaultHeight = mSize * scaledAspectRatio;
2487  scaledAspectRatio = defaultHeight / scaledSize;
2488 
2489  bool ok = true;
2490  double scaledHeight = scaledSize * scaledAspectRatio;
2492  {
2493  context.setOriginalValueVariable( defaultHeight );
2494  scaledHeight = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyHeight, context.renderContext().expressionContext(), defaultHeight, &ok );
2495  }
2496 
2497  if ( hasDataDefinedAspectRatio && ok )
2498  {
2499  switch ( mScaleMethod )
2500  {
2502  scaledHeight = sqrt( scaledHeight );
2503  break;
2505  break;
2506  }
2507  }
2508 
2509  scaledAspectRatio = scaledHeight / scaledSize;
2510 
2511  return scaledAspectRatio;
2512 }
2513 
2514 void QgsSvgMarkerSymbolLayer::calculateOffsetAndRotation( QgsSymbolRenderContext &context, double scaledWidth, double scaledHeight, QPointF &offset, double &angle ) const
2515 {
2516  //offset
2517  double offsetX = 0;
2518  double offsetY = 0;
2519  markerOffset( context, scaledWidth, scaledHeight, offsetX, offsetY );
2520  offset = QPointF( offsetX, offsetY );
2521 
2522  angle = mAngle + mLineAngle;
2524  {
2525  context.setOriginalValueVariable( mAngle );
2527  }
2528 
2530  if ( hasDataDefinedRotation )
2531  {
2532  // For non-point markers, "dataDefinedRotation" means following the
2533  // shape (shape-data defined). For them, "field-data defined" does
2534  // not work at all. TODO: if "field-data defined" ever gets implemented
2535  // we'll need a way to distinguish here between the two, possibly
2536  // using another flag in renderHints()
2537  const QgsFeature *f = context.feature();
2538  if ( f )
2539  {
2540  if ( f->hasGeometry() && f->geometry().type() == QgsWkbTypes::PointGeometry )
2541  {
2542  const QgsMapToPixel &m2p = context.renderContext().mapToPixel();
2543  angle += m2p.mapRotation();
2544  }
2545  }
2546  }
2547 
2548  if ( angle )
2550 }
2551 
2552 
2554 {
2555  QVariantMap map;
2556  map[QStringLiteral( "name" )] = mPath;
2557  map[QStringLiteral( "size" )] = QString::number( mSize );
2558  map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( mSizeUnit );
2559  map[QStringLiteral( "size_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
2560  map[QStringLiteral( "fixedAspectRatio" )] = QString::number( mFixedAspectRatio );
2561  map[QStringLiteral( "angle" )] = QString::number( mAngle );
2562  map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
2563  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
2564  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
2565  map[QStringLiteral( "scale_method" )] = QgsSymbolLayerUtils::encodeScaleMethod( mScaleMethod );
2566  map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
2567  map[QStringLiteral( "outline_color" )] = QgsSymbolLayerUtils::encodeColor( mStrokeColor );
2568  map[QStringLiteral( "outline_width" )] = QString::number( mStrokeWidth );
2569  map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( mStrokeWidthUnit );
2570  map[QStringLiteral( "outline_width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale );
2571  map[QStringLiteral( "horizontal_anchor_point" )] = QString::number( mHorizontalAnchorPoint );
2572  map[QStringLiteral( "vertical_anchor_point" )] = QString::number( mVerticalAnchorPoint );
2573 
2574  map[QStringLiteral( "parameters" )] = QgsProperty::propertyMapToVariantMap( mParameters );
2575 
2576  return map;
2577 }
2578 
2580 {
2584 }
2585 
2587 {
2590  m->setColor( mColor );
2595  m->setOffset( mOffset );
2596  m->setOffsetUnit( mOffsetUnit );
2598  m->setSizeUnit( mSizeUnit );
2602  m->setParameters( mParameters );
2603 
2605  copyPaintEffect( m );
2606  return m;
2607 }
2608 
2610 {
2612  mStrokeWidthUnit = unit;
2613 }
2614 
2616 {
2618  if ( unit != mStrokeWidthUnit )
2619  {
2621  }
2622  return unit;
2623 }
2624 
2626 {
2628  mStrokeWidthMapUnitScale = scale;
2629 }
2630 
2632 {
2634  {
2635  return mStrokeWidthMapUnitScale;
2636  }
2637  return QgsMapUnitScale();
2638 }
2639 
2640 void QgsSvgMarkerSymbolLayer::writeSldMarker( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2641 {
2642  // <Graphic>
2643  QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
2644  element.appendChild( graphicElem );
2645 
2646  // encode a parametric SVG reference
2647  const double size = QgsSymbolLayerUtils::rescaleUom( mSize, mSizeUnit, props );
2650 
2651  // <Rotation>
2652  QString angleFunc;
2653  bool ok;
2654  const double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
2655  if ( !ok )
2656  {
2657  angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
2658  }
2659  else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
2660  {
2661  angleFunc = QString::number( angle + mAngle );
2662  }
2663 
2664  QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
2665 
2666  // <Displacement>
2667  const QPointF offset = QgsSymbolLayerUtils::rescaleUom( mOffset, mOffsetUnit, props );
2669 }
2670 
2672 {
2673  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2674 
2675  QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
2676  if ( graphicElem.isNull() )
2677  return nullptr;
2678 
2679  QString path, mimeType;
2680  // Unused and to be DEPRECATED in externalGraphicFromSld
2681  QColor fillColor_;
2682  double size;
2683 
2684  if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor_, size ) )
2685  return nullptr;
2686 
2687  double scaleFactor = 1.0;
2688  const QString uom = element.attribute( QStringLiteral( "uom" ) );
2689  QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2690  size = size * scaleFactor;
2691 
2692  if ( mimeType != QLatin1String( "image/svg+xml" ) )
2693  return nullptr;
2694 
2695  double angle = 0.0;
2696  QString angleFunc;
2697  if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
2698  {
2699  bool ok;
2700  const double d = angleFunc.toDouble( &ok );
2701  if ( ok )
2702  angle = d;
2703  }
2704 
2705  QPointF offset;
2707 
2708  // Extract parameters from URL
2709  QString realPath { path };
2710  QUrl svgUrl { path };
2711 
2712  // Because color definition can start with '#', the url parsing won't recognize the query string entirely
2713  QUrlQuery queryString;
2714 
2715  if ( svgUrl.hasQuery() && svgUrl.hasFragment() )
2716  {
2717  const QString queryPart { path.mid( path.indexOf( '?' ) + 1 ) };
2718  queryString.setQuery( queryPart );
2719  }
2720 
2721  // Remove query for simple file paths
2722  if ( svgUrl.scheme().isEmpty() || svgUrl.isLocalFile() )
2723  {
2724  svgUrl.setQuery( QString() );
2725  realPath = svgUrl.path();
2726  }
2727 
2729 
2730  QMap<QString, QgsProperty> params;
2731 
2732  bool ok;
2733 
2734  if ( queryString.hasQueryItem( QStringLiteral( "fill" ) ) )
2735  {
2736  const QColor fillColor { queryString.queryItemValue( QStringLiteral( "fill" ) ) };
2737  m->setFillColor( fillColor );
2738  }
2739 
2740  if ( queryString.hasQueryItem( QStringLiteral( "fill-opacity" ) ) )
2741  {
2742  const double alpha { queryString.queryItemValue( QStringLiteral( "fill-opacity" ) ).toDouble( &ok ) };
2743  if ( ok )
2744  {
2745  params.insert( QStringLiteral( "fill-opacity" ), QgsProperty::fromValue( alpha ) );
2746  }
2747  }
2748 
2749  if ( queryString.hasQueryItem( QStringLiteral( "outline" ) ) )
2750  {
2751  const QColor strokeColor { queryString.queryItemValue( QStringLiteral( "outline" ) ) };
2753  }
2754 
2755  if ( queryString.hasQueryItem( QStringLiteral( "outline-opacity" ) ) )
2756  {
2757  const double alpha { queryString.queryItemValue( QStringLiteral( "outline-opacity" ) ).toDouble( &ok ) };
2758  if ( ok )
2759  {
2760  params.insert( QStringLiteral( "outline-opacity" ), QgsProperty::fromValue( alpha ) );
2761  }
2762  }
2763 
2764  if ( queryString.hasQueryItem( QStringLiteral( "outline-width" ) ) )
2765  {
2766  const int width { queryString.queryItemValue( QStringLiteral( "outline-width" ) ).toInt( &ok )};
2767  if ( ok )
2768  {
2769  m->setStrokeWidth( width );
2770  }
2771  }
2772 
2773  if ( ! params.isEmpty() )
2774  {
2775  m->setParameters( params );
2776  }
2777 
2778  m->setOutputUnit( sldUnitSize );
2779  m->setAngle( angle );
2780  m->setOffset( offset );
2781  return m;
2782 }
2783 
2784 bool QgsSvgMarkerSymbolLayer::writeDxf( QgsDxfExport &e, double mmMapUnitScaleFactor, const QString &layerName, QgsSymbolRenderContext &context, QPointF shift ) const
2785 {
2786  //size
2787  double size = mSize;
2788 
2789  const bool hasDataDefinedSize = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertySize );
2790 
2791  bool ok = true;
2792  if ( hasDataDefinedSize )
2793  {
2794  context.setOriginalValueVariable( mSize );
2796  }
2797 
2798  if ( hasDataDefinedSize && ok )
2799  {
2800  switch ( mScaleMethod )
2801  {
2803  size = std::sqrt( size );
2804  break;
2806  break;
2807  }
2808  }
2809 
2811  {
2812  size *= mmMapUnitScaleFactor;
2813  }
2814 
2815  //offset, angle
2816  QPointF offset = mOffset;
2817 
2819  {
2821  const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
2822  const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
2823  if ( ok )
2824  offset = res;
2825  }
2826  const double offsetX = offset.x();
2827  const double offsetY = offset.y();
2828 
2829  QPointF outputOffset( offsetX, offsetY );
2830 
2831  double angle = mAngle + mLineAngle;
2833  {
2834  context.setOriginalValueVariable( mAngle );
2836  }
2837 
2838  if ( angle )
2839  outputOffset = _rotatedOffset( outputOffset, angle );
2840 
2842 
2843  QString path = mPath;
2845  {
2846  context.setOriginalValueVariable( mPath );
2848  context.renderContext().pathResolver() );
2849  }
2850 
2851  double strokeWidth = mStrokeWidth;
2853  {
2856  }
2858 
2859  QColor fillColor = mColor;
2861  {
2864  }
2865 
2866  QColor strokeColor = mStrokeColor;
2868  {
2871  }
2872 
2874 
2875  const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, size, fillColor, strokeColor, strokeWidth,
2877  ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), evaluatedParameters );
2878 
2879  QSvgRenderer r( svgContent );
2880  if ( !r.isValid() )
2881  return false;
2882 
2883  QgsDxfPaintDevice pd( &e );
2884  pd.setDrawingSize( QSizeF( r.defaultSize() ) );
2885 
2886  QSizeF outSize( r.defaultSize() );
2887  outSize.scale( size, size, Qt::KeepAspectRatio );
2888 
2889  QPainter p;
2890  p.begin( &pd );
2891  if ( !qgsDoubleNear( angle, 0.0 ) )
2892  {
2893  p.translate( r.defaultSize().width() / 2.0, r.defaultSize().height() / 2.0 );
2894  p.rotate( angle );
2895  p.translate( -r.defaultSize().width() / 2.0, -r.defaultSize().height() / 2.0 );
2896  }
2897  pd.setShift( shift + QPointF( outputOffset.x(), -outputOffset.y() ) );
2898  pd.setOutputSize( QRectF( -outSize.width() / 2.0, -outSize.height() / 2.0, outSize.width(), outSize.height() ) );
2899  pd.setLayer( layerName );
2900  r.render( &p );
2901  p.end();
2902  return true;
2903 }
2904 
2906 {
2907  bool hasDataDefinedSize = false;
2908  double scaledWidth = calculateSize( context, hasDataDefinedSize );
2909 
2910  bool hasDataDefinedAspectRatio = false;
2911  const double aspectRatio = calculateAspectRatio( context, scaledWidth, hasDataDefinedAspectRatio );
2912  double scaledHeight = scaledWidth * ( !qgsDoubleNear( aspectRatio, 0.0 ) ? aspectRatio : mDefaultAspectRatio );
2913 
2914  scaledWidth = context.renderContext().convertToPainterUnits( scaledWidth, mSizeUnit, mSizeMapUnitScale );
2915  scaledHeight = context.renderContext().convertToPainterUnits( scaledHeight, mSizeUnit, mSizeMapUnitScale );
2916 
2917  //don't render symbols with size below one or above 10,000 pixels
2918  if ( static_cast< int >( scaledWidth ) < 1 || 10000.0 < scaledWidth )
2919  {
2920  return QRectF();
2921  }
2922 
2923  QPointF outputOffset;
2924  double angle = 0.0;
2925  calculateOffsetAndRotation( context, scaledWidth, scaledHeight, outputOffset, angle );
2926 
2927  double strokeWidth = mStrokeWidth;
2929  {
2932  }
2934 
2935  QString path = mPath;
2937  {
2938  context.setOriginalValueVariable( mPath );
2940  context.renderContext().pathResolver() );
2942  {
2943  // need to get colors to take advantage of cached SVGs
2944  QColor fillColor = mColor;
2946  {
2949  }
2950 
2951  const QColor strokeColor = mStrokeColor;
2953  {
2956  }
2957 
2959 
2960  // adjust height of data defined path
2961  const QSizeF svgViewbox = QgsApplication::svgCache()->svgViewboxSize( path, scaledWidth, fillColor, strokeColor, strokeWidth,
2962  context.renderContext().scaleFactor(), aspectRatio,
2963  ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), evaluatedParameters );
2964  scaledHeight = svgViewbox.isValid() ? scaledWidth * svgViewbox.height() / svgViewbox.width() : scaledWidth;
2965  }
2966  }
2967 
2968  QTransform transform;
2969  // move to the desired position
2970  transform.translate( point.x() + outputOffset.x(), point.y() + outputOffset.y() );
2971 
2972  if ( !qgsDoubleNear( angle, 0.0 ) )
2973  transform.rotate( angle );
2974 
2975  //antialiasing
2976  strokeWidth += 1.0 / 2.0;
2977 
2978  QRectF symbolBounds = transform.mapRect( QRectF( -scaledWidth / 2.0,
2979  -scaledHeight / 2.0,
2980  scaledWidth,
2981  scaledHeight ) );
2982 
2983  //extend bounds by pen width / 2.0
2984  symbolBounds.adjust( -strokeWidth / 2.0, -strokeWidth / 2.0,
2985  strokeWidth / 2.0, strokeWidth / 2.0 );
2986 
2987  return symbolBounds;
2988 }
2989 
2991 
2992 QgsRasterMarkerSymbolLayer::QgsRasterMarkerSymbolLayer( const QString &path, double size, double angle, Qgis::ScaleMethod scaleMethod )
2993  : mPath( path )
2994 {
2995  mSize = size;
2996  mAngle = angle;
2997  mOffset = QPointF( 0, 0 );
3000 }
3001 
3003 
3005 {
3006  QString path;
3010 
3011  if ( props.contains( QStringLiteral( "imageFile" ) ) )
3012  path = props[QStringLiteral( "imageFile" )].toString();
3013  if ( props.contains( QStringLiteral( "size" ) ) )
3014  size = props[QStringLiteral( "size" )].toDouble();
3015  if ( props.contains( QStringLiteral( "angle" ) ) )
3016  angle = props[QStringLiteral( "angle" )].toDouble();
3017  if ( props.contains( QStringLiteral( "scale_method" ) ) )
3018  scaleMethod = QgsSymbolLayerUtils::decodeScaleMethod( props[QStringLiteral( "scale_method" )].toString() );
3019 
3020  std::unique_ptr< QgsRasterMarkerSymbolLayer > m = std::make_unique< QgsRasterMarkerSymbolLayer >( path, size, angle, scaleMethod );
3021  m->setCommonProperties( props );
3022  return m.release();
3023 }
3024 
3025 void QgsRasterMarkerSymbolLayer::setCommonProperties( const QVariantMap &properties )
3026 {
3027  if ( properties.contains( QStringLiteral( "alpha" ) ) )
3028  {
3029  setOpacity( properties[QStringLiteral( "alpha" )].toDouble() );
3030  }
3031 
3032  if ( properties.contains( QStringLiteral( "size_unit" ) ) )
3033  setSizeUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "size_unit" )].toString() ) );
3034  if ( properties.contains( QStringLiteral( "size_map_unit_scale" ) ) )
3035  setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "size_map_unit_scale" )].toString() ) );
3036  if ( properties.contains( QStringLiteral( "fixedAspectRatio" ) ) )
3037  setFixedAspectRatio( properties[QStringLiteral( "fixedAspectRatio" )].toDouble() );
3038 
3039  if ( properties.contains( QStringLiteral( "offset" ) ) )
3040  setOffset( QgsSymbolLayerUtils::decodePoint( properties[QStringLiteral( "offset" )].toString() ) );
3041  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3042  setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3043  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3044  setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3045 
3046  if ( properties.contains( QStringLiteral( "horizontal_anchor_point" ) ) )
3047  {
3048  setHorizontalAnchorPoint( QgsMarkerSymbolLayer::HorizontalAnchorPoint( properties[ QStringLiteral( "horizontal_anchor_point" )].toInt() ) );
3049  }
3050  if ( properties.contains( QStringLiteral( "vertical_anchor_point" ) ) )
3051  {
3052  setVerticalAnchorPoint( QgsMarkerSymbolLayer::VerticalAnchorPoint( properties[ QStringLiteral( "vertical_anchor_point" )].toInt() ) );
3053  }
3054 
3057 }
3058 
3059 void QgsRasterMarkerSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3060 {
3061  const QVariantMap::iterator it = properties.find( QStringLiteral( "name" ) );
3062  if ( it != properties.end() && it.value().type() == QVariant::String )
3063  {
3064  if ( saving )
3065  it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3066  else
3067  it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3068  }
3069 }
3070 
3071 void QgsRasterMarkerSymbolLayer::setPath( const QString &path )
3072 {
3073  mPath = path;
3075 }
3076 
3078 {
3079  const bool aPreservedAspectRatio = preservedAspectRatio();
3080  if ( aPreservedAspectRatio && !par )
3081  {
3083  }
3084  else if ( !aPreservedAspectRatio && par )
3085  {
3086  mFixedAspectRatio = 0.0;
3087  }
3088  return preservedAspectRatio();
3089 }
3090 
3092 {
3093  if ( mDefaultAspectRatio == 0.0 )
3094  {
3095  const QSize size = QgsApplication::imageCache()->originalSize( mPath );
3096  mDefaultAspectRatio = ( !size.isNull() && size.isValid() && size.width() > 0 ) ? static_cast< double >( size.height() ) / static_cast< double >( size.width() ) : 0.0;
3097  }
3098  return mDefaultAspectRatio;
3099 }
3100 
3102 {
3103  return QStringLiteral( "RasterMarker" );
3104 }
3105 
3107 {
3108  QPainter *p = context.renderContext().painter();
3109  if ( !p )
3110  return;
3111 
3112  QString path = mPath;
3114  {
3115  context.setOriginalValueVariable( mPath );
3117  }
3118 
3119  if ( path.isEmpty() )
3120  return;
3121 
3122  double width = 0.0;
3123  double height = 0.0;
3124 
3125  bool hasDataDefinedSize = false;
3126  const double scaledSize = calculateSize( context, hasDataDefinedSize );
3127 
3128  bool hasDataDefinedAspectRatio = false;
3129  const double aspectRatio = calculateAspectRatio( context, scaledSize, hasDataDefinedAspectRatio );
3130 
3131  QPointF outputOffset;
3132  double angle = 0.0;
3133 
3134  // RenderPercentage Unit Type takes original image size
3136  {
3137  const QSize size = QgsApplication::imageCache()->originalSize( path );
3138  if ( size.isEmpty() )
3139  return;
3140 
3141  width = ( scaledSize * static_cast< double >( size.width() ) ) / 100.0;
3142  height = ( scaledSize * static_cast< double >( size.height() ) ) / 100.0;
3143 
3144  // don't render symbols with size below one or above 10,000 pixels
3145  if ( static_cast< int >( width ) < 1 || 10000.0 < width || static_cast< int >( height ) < 1 || 10000.0 < height )
3146  return;
3147 
3148  calculateOffsetAndRotation( context, width, height, outputOffset, angle );
3149  }
3150  else
3151  {
3152  width = context.renderContext().convertToPainterUnits( scaledSize, mSizeUnit, mSizeMapUnitScale );
3153  height = width * ( preservedAspectRatio() ? defaultAspectRatio() : aspectRatio );
3154 
3155  if ( preservedAspectRatio() && path != mPath )
3156  {
3157  const QSize size = QgsApplication::imageCache()->originalSize( path );
3158  if ( !size.isNull() && size.isValid() && size.width() > 0 )
3159  {
3160  height = width * ( static_cast< double >( size.height() ) / static_cast< double >( size.width() ) );
3161  }
3162  }
3163 
3164  // don't render symbols with size below one or above 10,000 pixels
3165  if ( static_cast< int >( width ) < 1 || 10000.0 < width )
3166  return;
3167 
3168  calculateOffsetAndRotation( context, scaledSize, scaledSize * ( height / width ), outputOffset, angle );
3169  }
3170 
3171  const QgsScopedQPainterState painterState( p );
3172  p->translate( point + outputOffset );
3173 
3174  const bool rotated = !qgsDoubleNear( angle, 0 );
3175  if ( rotated )
3176  p->rotate( angle );
3177 
3178  double opacity = mOpacity;
3180  {
3183  }
3184  opacity *= context.opacity();
3185 
3186  QImage img = fetchImage( context.renderContext(), path, QSize( width, preservedAspectRatio() ? 0 : width * aspectRatio ), preservedAspectRatio(), opacity );
3187  if ( !img.isNull() )
3188  {
3189  if ( context.selected() )
3190  {
3192  }
3193 
3194  p->drawImage( -img.width() / 2.0, -img.height() / 2.0, img );
3195  }
3196 }
3197 
3198 QImage QgsRasterMarkerSymbolLayer::fetchImage( QgsRenderContext &context, const QString &path, QSize size, bool preserveAspectRatio, double opacity ) const
3199 {
3200  bool cached = false;
3201  return QgsApplication::imageCache()->pathAsImage( path, size, preserveAspectRatio, opacity, cached, context.flags() & Qgis::RenderContextFlag::RenderBlocking );
3202 }
3203 
3204 double QgsRasterMarkerSymbolLayer::calculateSize( QgsSymbolRenderContext &context, bool &hasDataDefinedSize ) const
3205 {
3206  double scaledSize = mSize;
3208 
3209  bool ok = true;
3210  if ( hasDataDefinedSize )
3211  {
3212  context.setOriginalValueVariable( mSize );
3214  }
3215  else
3216  {
3218  if ( hasDataDefinedSize )
3219  {
3220  context.setOriginalValueVariable( mSize );
3222  }
3223  }
3224 
3225  if ( hasDataDefinedSize && ok )
3226  {
3227  switch ( mScaleMethod )
3228  {
3230  scaledSize = std::sqrt( scaledSize );
3231  break;
3233  break;
3234  }
3235  }
3236 
3237  return scaledSize;
3238 }
3239 
3240 double QgsRasterMarkerSymbolLayer::calculateAspectRatio( QgsSymbolRenderContext &context, double scaledSize, bool &hasDataDefinedAspectRatio ) const
3241 {
3243  if ( !hasDataDefinedAspectRatio )
3244  return mFixedAspectRatio;
3245 
3247  return 0.0;
3248 
3249  double scaledAspectRatio = mDefaultAspectRatio;
3250  if ( mFixedAspectRatio > 0.0 )
3251  scaledAspectRatio = mFixedAspectRatio;
3252 
3253  const double defaultHeight = mSize * scaledAspectRatio;
3254  scaledAspectRatio = defaultHeight / scaledSize;
3255 
3256  bool ok = true;
3257  double scaledHeight = scaledSize * scaledAspectRatio;
3259  {
3260  context.setOriginalValueVariable( defaultHeight );
3261  scaledHeight = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyHeight, context.renderContext().expressionContext(), defaultHeight, &ok );
3262  }
3263 
3264  if ( hasDataDefinedAspectRatio && ok )
3265  {
3266  switch ( mScaleMethod )
3267  {
3269  scaledHeight = sqrt( scaledHeight );
3270  break;
3272  break;
3273  }
3274  }
3275 
3276  scaledAspectRatio = scaledHeight / scaledSize;
3277 
3278  return scaledAspectRatio;
3279 }
3280 
3281 void QgsRasterMarkerSymbolLayer::calculateOffsetAndRotation( QgsSymbolRenderContext &context, double scaledWidth, double scaledHeight, QPointF &offset, double &angle ) const
3282 {
3283  //offset
3284  double offsetX = 0;
3285  double offsetY = 0;
3286  markerOffset( context, scaledWidth, scaledHeight, offsetX, offsetY );
3287  offset = QPointF( offsetX, offsetY );
3288 
3289  angle = mAngle + mLineAngle;
3291  {
3292  context.setOriginalValueVariable( mAngle );
3294  }
3295 
3297  if ( hasDataDefinedRotation )
3298  {
3299  const QgsFeature *f = context.feature();
3300  if ( f )
3301  {
3302  if ( f->hasGeometry() && f->geometry().type() == QgsWkbTypes::PointGeometry )
3303  {
3304  const QgsMapToPixel &m2p = context.renderContext().mapToPixel();
3305  angle += m2p.mapRotation();
3306  }
3307  }
3308  }
3309 
3310  if ( angle )
3312 }
3313 
3314 
3316 {
3317  QVariantMap map;
3318  map[QStringLiteral( "imageFile" )] = mPath;
3319  map[QStringLiteral( "size" )] = QString::number( mSize );
3320  map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( mSizeUnit );
3321  map[QStringLiteral( "size_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
3322  map[QStringLiteral( "fixedAspectRatio" )] = QString::number( mFixedAspectRatio );
3323  map[QStringLiteral( "angle" )] = QString::number( mAngle );
3324  map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
3325  map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
3326  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3327  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3328  map[QStringLiteral( "scale_method" )] = QgsSymbolLayerUtils::encodeScaleMethod( mScaleMethod );
3329  map[QStringLiteral( "horizontal_anchor_point" )] = QString::number( mHorizontalAnchorPoint );
3330  map[QStringLiteral( "vertical_anchor_point" )] = QString::number( mVerticalAnchorPoint );
3331  return map;
3332 }
3333 
3335 {
3336  std::unique_ptr< QgsRasterMarkerSymbolLayer > m = std::make_unique< QgsRasterMarkerSymbolLayer >( mPath, mSize, mAngle );
3337  copyCommonProperties( m.get() );
3338  return m.release();
3339 }
3340 
3341 
3343 {
3345  other->setOpacity( mOpacity );
3346  other->setOffset( mOffset );
3347  other->setOffsetUnit( mOffsetUnit );
3349  other->setSizeUnit( mSizeUnit );
3353  copyDataDefinedProperties( other );
3354  copyPaintEffect( other );
3355 }
3356 
3358 {
3361 }
3362 
3364 {
3365  return QColor();
3366 }
3367 
3369 {
3371 }
3372 
3374 {
3376 }
3377 
3379 {
3380  bool hasDataDefinedSize = false;
3381  const double scaledSize = calculateSize( context, hasDataDefinedSize );
3382  const double width = context.renderContext().convertToPainterUnits( scaledSize, mSizeUnit, mSizeMapUnitScale );
3383  bool hasDataDefinedAspectRatio = false;
3384  const double aspectRatio = calculateAspectRatio( context, scaledSize, hasDataDefinedAspectRatio );
3385  const double height = width * ( preservedAspectRatio() ? defaultAspectRatio() : aspectRatio );
3386 
3387  //don't render symbols with size below one or above 10,000 pixels
3388  if ( static_cast< int >( scaledSize ) < 1 || 10000.0 < scaledSize )
3389  {
3390  return QRectF();
3391  }
3392 
3393  QPointF outputOffset;
3394  double angle = 0.0;
3395  calculateOffsetAndRotation( context, scaledSize, scaledSize * ( height / width ), outputOffset, angle );
3396 
3397  QTransform transform;
3398 
3399  // move to the desired position
3400  transform.translate( point.x() + outputOffset.x(), point.y() + outputOffset.y() );
3401 
3402  if ( !qgsDoubleNear( angle, 0.0 ) )
3403  transform.rotate( angle );
3404 
3405  QRectF symbolBounds = transform.mapRect( QRectF( -width / 2.0,
3406  -height / 2.0,
3407  width,
3408  height ) );
3409 
3410  return symbolBounds;
3411 }
3412 
3414 
3415 QgsFontMarkerSymbolLayer::QgsFontMarkerSymbolLayer( const QString &fontFamily, QString chr, double pointSize, const QColor &color, double angle )
3416 {
3417  mFontFamily = fontFamily;
3418  mString = chr;
3419  mColor = color;
3420  mAngle = angle;
3421  mSize = pointSize;
3422  mOrigSize = pointSize;
3424  mOffset = QPointF( 0, 0 );
3426  mStrokeColor = DEFAULT_FONTMARKER_BORDERCOLOR;
3427  mStrokeWidth = 0.0;
3428  mStrokeWidthUnit = QgsUnitTypes::RenderMillimeters;
3429  mPenJoinStyle = DEFAULT_FONTMARKER_JOINSTYLE;
3430 }
3431 
3433 
3435 {
3437  QString string = DEFAULT_FONTMARKER_CHR;
3438  double pointSize = DEFAULT_FONTMARKER_SIZE;
3441 
3442  if ( props.contains( QStringLiteral( "font" ) ) )
3443  fontFamily = props[QStringLiteral( "font" )].toString();
3444  if ( props.contains( QStringLiteral( "chr" ) ) && props[QStringLiteral( "chr" )].toString().length() > 0 )
3445  string = props[QStringLiteral( "chr" )].toString();
3446  if ( props.contains( QStringLiteral( "size" ) ) )
3447  pointSize = props[QStringLiteral( "size" )].toDouble();
3448  if ( props.contains( QStringLiteral( "color" ) ) )
3449  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
3450  if ( props.contains( QStringLiteral( "angle" ) ) )
3451  angle = props[QStringLiteral( "angle" )].toDouble();
3452 
3453  QgsFontMarkerSymbolLayer *m = new QgsFontMarkerSymbolLayer( fontFamily, string, pointSize, color, angle );
3454 
3455  if ( props.contains( QStringLiteral( "font_style" ) ) )
3456  m->setFontStyle( props[QStringLiteral( "font_style" )].toString() );
3457  if ( props.contains( QStringLiteral( "outline_color" ) ) )
3458  m->setStrokeColor( QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline_color" )].toString() ) );
3459  if ( props.contains( QStringLiteral( "outline_width" ) ) )
3460  m->setStrokeWidth( props[QStringLiteral( "outline_width" )].toDouble() );
3461  if ( props.contains( QStringLiteral( "offset" ) ) )
3462  m->setOffset( QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() ) );
3463  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
3464  m->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
3465  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3466  m->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3467  if ( props.contains( QStringLiteral( "size_unit" ) ) )
3468  m->setSizeUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "size_unit" )].toString() ) );
3469  if ( props.contains( QStringLiteral( "size_map_unit_scale" ) ) )
3470  m->setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "size_map_unit_scale" )].toString() ) );
3471  if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
3472  m->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
3473  if ( props.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
3474  m->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
3475  if ( props.contains( QStringLiteral( "joinstyle" ) ) )
3476  m->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
3477  if ( props.contains( QStringLiteral( "horizontal_anchor_point" ) ) )
3478  m->setHorizontalAnchorPoint( QgsMarkerSymbolLayer::HorizontalAnchorPoint( props[ QStringLiteral( "horizontal_anchor_point" )].toInt() ) );
3479  if ( props.contains( QStringLiteral( "vertical_anchor_point" ) ) )
3480  m->setVerticalAnchorPoint( QgsMarkerSymbolLayer::VerticalAnchorPoint( props[ QStringLiteral( "vertical_anchor_point" )].toInt() ) );
3481 
3482  m->restoreOldDataDefinedProperties( props );
3483 
3484  return m;
3485 }
3486 
3488 {
3489  return QStringLiteral( "FontMarker" );
3490 }
3491 
3493 {
3494  QColor brushColor = mColor;
3495  QColor penColor = mStrokeColor;
3496 
3497  brushColor.setAlphaF( mColor.alphaF() * context.opacity() );
3498  penColor.setAlphaF( mStrokeColor.alphaF() * context.opacity() );
3499 
3500  mBrush = QBrush( brushColor );
3501  mPen = QPen( penColor );
3502  mPen.setJoinStyle( mPenJoinStyle );
3503  mPen.setWidthF( context.renderContext().convertToPainterUnits( mStrokeWidth, mStrokeWidthUnit, mStrokeWidthMapUnitScale ) );
3504 
3505  mFont = QFont( QgsApplication::fontManager()->processFontFamilyName( mFontFamily ) );
3506  if ( !mFontStyle.isEmpty() )
3507  {
3508  mFont.setStyleName( QgsFontUtils::translateNamedStyle( mFontStyle ) );
3509  }
3510 
3511  double sizePixels = context.renderContext().convertToPainterUnits( mSize, mSizeUnit, mSizeMapUnitScale );
3512  mNonZeroFontSize = !qgsDoubleNear( sizePixels, 0.0 );
3513 
3514  if ( mNonZeroFontSize && sizePixels > MAX_FONT_CHARACTER_SIZE_IN_PIXELS )
3515  {
3516  // if font is too large (e.g using map units and map is very zoomed in), then we limit
3517  // the font size and instead scale up the painter.
3518  // this avoids issues with massive font sizes (eg https://github.com/qgis/QGIS/issues/42270)
3519  mFontSizeScale = sizePixels / MAX_FONT_CHARACTER_SIZE_IN_PIXELS;
3520  sizePixels = MAX_FONT_CHARACTER_SIZE_IN_PIXELS;
3521  }
3522  else
3523  mFontSizeScale = 1.0;
3524 
3525  // if a non zero, but small pixel size results, round up to 2 pixels so that a "dot" is at least visible
3526  // (if we set a <=1 pixel size here Qt will reset the font to a default size, leading to much too large symbols)
3527  mFont.setPixelSize( std::max( 2, static_cast< int >( std::round( sizePixels ) ) ) );
3528  mFontMetrics.reset( new QFontMetrics( mFont ) );
3529  mChrWidth = mFontMetrics->horizontalAdvance( mString );
3530  mChrOffset = QPointF( mChrWidth / 2.0, -mFontMetrics->ascent() / 2.0 );
3531  mOrigSize = mSize; // save in case the size would be data defined
3532 
3533  // use caching only when not using a data defined character
3537  if ( mUseCachedPath )
3538  {
3539  QPointF chrOffset = mChrOffset;
3540  double chrWidth;
3541  const QString charToRender = characterToRender( context, chrOffset, chrWidth );
3542  mCachedPath = QPainterPath();
3543  mCachedPath.addText( -chrOffset.x(), -chrOffset.y(), mFont, charToRender );
3544  }
3545 }
3546 
3548 {
3549  Q_UNUSED( context )
3550 }
3551 
3552 QString QgsFontMarkerSymbolLayer::characterToRender( QgsSymbolRenderContext &context, QPointF &charOffset, double &charWidth )
3553 {
3554  charOffset = mChrOffset;
3555  QString stringToRender = mString;
3557  {
3558  context.setOriginalValueVariable( mString );
3560  if ( stringToRender != mString )
3561  {
3562  charWidth = mFontMetrics->horizontalAdvance( stringToRender );
3563  charOffset = QPointF( charWidth / 2.0, -mFontMetrics->ascent() / 2.0 );
3564  }
3565  }
3566  return stringToRender;
3567 }
3568 
3569 void QgsFontMarkerSymbolLayer::calculateOffsetAndRotation( QgsSymbolRenderContext &context,
3570  double scaledSize,
3571  bool &hasDataDefinedRotation,
3572  QPointF &offset,
3573  double &angle ) const
3574 {
3575  //offset
3576  double offsetX = 0;
3577  double offsetY = 0;
3578  markerOffset( context, scaledSize, scaledSize, offsetX, offsetY );
3579  offset = QPointF( offsetX, offsetY );
3580 
3581  //angle
3582  bool ok = true;
3583  angle = mAngle + mLineAngle;
3585  {
3586  context.setOriginalValueVariable( angle );
3588 
3589  // If the expression evaluation was not successful, fallback to static value
3590  if ( !ok )
3591  angle = mAngle + mLineAngle;
3592  }
3593 
3594  hasDataDefinedRotation = context.renderHints() & Qgis::SymbolRenderHint::DynamicRotation;
3595  if ( hasDataDefinedRotation )
3596  {
3597  // For non-point markers, "dataDefinedRotation" means following the
3598  // shape (shape-data defined). For them, "field-data defined" does
3599  // not work at all. TODO: if "field-data defined" ever gets implemented
3600  // we'll need a way to distinguish here between the two, possibly
3601  // using another flag in renderHints()
3602  const QgsFeature *f = context.feature();
3603  if ( f )
3604  {
3605  if ( f->hasGeometry() && f->geometry().type() == QgsWkbTypes::PointGeometry )
3606  {
3607  const QgsMapToPixel &m2p = context.renderContext().mapToPixel();
3608  angle += m2p.mapRotation();
3609  }
3610  }
3611  }
3612 
3613  if ( angle )
3615 }
3616 
3617 double QgsFontMarkerSymbolLayer::calculateSize( QgsSymbolRenderContext &context )
3618 {
3619  double scaledSize = mSize;
3620  const bool hasDataDefinedSize = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertySize );
3621 
3622  bool ok = true;
3623  if ( hasDataDefinedSize )
3624  {
3625  context.setOriginalValueVariable( mSize );
3627  }
3628 
3629  if ( hasDataDefinedSize && ok )
3630  {
3631  switch ( mScaleMethod )
3632  {
3634  scaledSize = std::sqrt( scaledSize );
3635  break;
3637  break;
3638  }
3639  }
3640  return scaledSize;
3641 }
3642 
3644 {
3645  QPainter *p = context.renderContext().painter();
3646  if ( !p || !mNonZeroFontSize )
3647  return;
3648 
3649  QTransform transform;
3650 
3651  bool ok;
3652  QColor brushColor = mColor;
3654  {
3657  }
3658  brushColor = context.selected() ? context.renderContext().selectionColor() : brushColor;
3659  if ( !context.selected() || !SELECTION_IS_OPAQUE )
3660  {
3661  brushColor.setAlphaF( brushColor.alphaF() * context.opacity() );
3662  }
3663  mBrush.setColor( brushColor );
3664 
3665  QColor penColor = mStrokeColor;
3667  {
3670  }
3671  penColor.setAlphaF( penColor.alphaF() * context.opacity() );
3672 
3673  double penWidth = context.renderContext().convertToPainterUnits( mStrokeWidth, mStrokeWidthUnit, mStrokeWidthMapUnitScale );
3675  {
3676  context.setOriginalValueVariable( mStrokeWidth );
3678  if ( ok )
3679  {
3680  penWidth = context.renderContext().convertToPainterUnits( strokeWidth, mStrokeWidthUnit, mStrokeWidthMapUnitScale );
3681  }
3682  }
3683 
3685  {
3687  const QString style = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyJoinStyle, context.renderContext().expressionContext(), QString(), &ok );
3688  if ( ok )
3689  {
3690  mPen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
3691  }
3692  }
3693 
3694  const QgsScopedQPainterState painterState( p );
3695  p->setBrush( mBrush );
3696  if ( !qgsDoubleNear( penWidth, 0.0 ) )
3697  {
3698  mPen.setColor( penColor );
3699  mPen.setWidthF( penWidth );
3700  p->setPen( mPen );
3701  }
3702  else
3703  {
3704  p->setPen( Qt::NoPen );
3705  }
3706 
3708  {
3709  context.setOriginalValueVariable( mFontFamily );
3711  const QString processedFamily = QgsApplication::fontManager()->processFontFamilyName( ok ? fontFamily : mFontFamily );
3712  mFont.setFamily( processedFamily );
3713  }
3715  {
3716  context.setOriginalValueVariable( mFontStyle );
3719  }
3721  {
3722  mFontMetrics.reset( new QFontMetrics( mFont ) );
3723  }
3724 
3725  QPointF chrOffset = mChrOffset;
3726  double chrWidth;
3727  const QString charToRender = characterToRender( context, chrOffset, chrWidth );
3728 
3729  const double sizeToRender = calculateSize( context );
3730 
3731  bool hasDataDefinedRotation = false;
3732  QPointF offset;
3733  double angle = 0;
3734  calculateOffsetAndRotation( context, sizeToRender, hasDataDefinedRotation, offset, angle );
3735 
3736  p->translate( point.x() + offset.x(), point.y() + offset.y() );
3737 
3738  if ( !qgsDoubleNear( angle, 0.0 ) )
3739  transform.rotate( angle );
3740 
3741  if ( !qgsDoubleNear( sizeToRender, mOrigSize ) )
3742  {
3743  const double s = sizeToRender / mOrigSize;
3744  transform.scale( s, s );
3745  }
3746 
3747  if ( !qgsDoubleNear( mFontSizeScale, 1.0 ) )
3748  transform.scale( mFontSizeScale, mFontSizeScale );
3749 
3750  if ( mUseCachedPath )
3751  {
3752  p->drawPath( transform.map( mCachedPath ) );
3753  }
3754  else
3755  {
3756  QPainterPath path;
3757  path.addText( -chrOffset.x(), -chrOffset.y(), mFont, charToRender );
3758  p->drawPath( transform.map( path ) );
3759  }
3760 }
3761 
3763 {
3764  QVariantMap props;
3765  props[QStringLiteral( "font" )] = mFontFamily;
3766  props[QStringLiteral( "font_style" )] = mFontStyle;
3767  props[QStringLiteral( "chr" )] = mString;
3768  props[QStringLiteral( "size" )] = QString::number( mSize );
3769  props[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( mSizeUnit );
3770  props[QStringLiteral( "size_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
3771  props[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
3772  props[QStringLiteral( "outline_color" )] = QgsSymbolLayerUtils::encodeColor( mStrokeColor );
3773  props[QStringLiteral( "outline_width" )] = QString::number( mStrokeWidth );
3774  props[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( mStrokeWidthUnit );
3775  props[QStringLiteral( "outline_width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale );
3776  props[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3777  props[QStringLiteral( "angle" )] = QString::number( mAngle );
3778  props[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
3779  props[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3780  props[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3781  props[QStringLiteral( "horizontal_anchor_point" )] = QString::number( mHorizontalAnchorPoint );
3782  props[QStringLiteral( "vertical_anchor_point" )] = QString::number( mVerticalAnchorPoint );
3783  return props;
3784 }
3785 
3787 {
3788  QgsFontMarkerSymbolLayer *m = new QgsFontMarkerSymbolLayer( mFontFamily, mString, mSize, mColor, mAngle );
3789  m->setFontStyle( mFontStyle );
3790  m->setStrokeColor( mStrokeColor );
3791  m->setStrokeWidth( mStrokeWidth );
3792  m->setStrokeWidthUnit( mStrokeWidthUnit );
3793  m->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
3794  m->setPenJoinStyle( mPenJoinStyle );
3795  m->setOffset( mOffset );
3796  m->setOffsetUnit( mOffsetUnit );
3798  m->setSizeUnit( mSizeUnit );
3803  copyPaintEffect( m );
3804  return m;
3805 }
3806 
3807 void QgsFontMarkerSymbolLayer::writeSldMarker( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
3808 {
3809  // <Graphic>
3810  QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
3811  element.appendChild( graphicElem );
3812 
3813  const QString fontPath = QStringLiteral( "ttf://%1" ).arg( mFontFamily );
3814  int markIndex = !mString.isEmpty() ? mString.at( 0 ).unicode() : 0;
3815  const double size = QgsSymbolLayerUtils::rescaleUom( mSize, mSizeUnit, props );
3816  QgsSymbolLayerUtils::externalMarkerToSld( doc, graphicElem, fontPath, QStringLiteral( "ttf" ), &markIndex, mColor, size );
3817 
3818  // <Rotation>
3819  QString angleFunc;
3820  bool ok;
3821  const double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
3822  if ( !ok )
3823  {
3824  angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
3825  }
3826  else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
3827  {
3828  angleFunc = QString::number( angle + mAngle );
3829  }
3830  QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
3831 
3832  // <Displacement>
3833  const QPointF offset = QgsSymbolLayerUtils::rescaleUom( mOffset, mOffsetUnit, props );
3835 }
3836 
3838 {
3840  || mStrokeWidthUnit == QgsUnitTypes::RenderMapUnits || mStrokeWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
3842 }
3843 
3845 {
3847  mStrokeWidthUnit = unit;
3848 }
3849 
3851 {
3852  QPointF chrOffset = mChrOffset;
3853  double chrWidth = mChrWidth;
3854  //calculate width of rendered character
3855  ( void )characterToRender( context, chrOffset, chrWidth );
3856 
3857  if ( !mFontMetrics )
3858  mFontMetrics.reset( new QFontMetrics( mFont ) );
3859 
3860  double scaledSize = calculateSize( context );
3861  if ( !qgsDoubleNear( scaledSize, mOrigSize ) )
3862  {
3863  chrWidth *= scaledSize / mOrigSize;
3864  }
3865  chrWidth *= mFontSizeScale;
3866 
3867  bool hasDataDefinedRotation = false;
3868  QPointF offset;
3869  double angle = 0;
3870  calculateOffsetAndRotation( context, scaledSize, hasDataDefinedRotation, offset, angle );
3871  scaledSize = context.renderContext().convertToPainterUnits( scaledSize, mSizeUnit, mSizeMapUnitScale );
3872 
3873  QTransform transform;
3874 
3875  // move to the desired position
3876  transform.translate( point.x() + offset.x(), point.y() + offset.y() );
3877 
3878  if ( !qgsDoubleNear( angle, 0.0 ) )
3879  transform.rotate( angle );
3880 
3881  QRectF symbolBounds = transform.mapRect( QRectF( -chrWidth / 2.0,
3882  -scaledSize / 2.0,
3883  chrWidth,
3884  scaledSize ) );
3885  return symbolBounds;
3886 }
3887 
3889 {
3890  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
3891 
3892  QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
3893  if ( graphicElem.isNull() )
3894  return nullptr;
3895 
3896  QString name, format;
3897  QColor color;
3898  double size;
3899  int chr;
3900 
3901  if ( !QgsSymbolLayerUtils::externalMarkerFromSld( graphicElem, name, format, chr, color, size ) )
3902  return nullptr;
3903 
3904  if ( !name.startsWith( QLatin1String( "ttf://" ) ) || format != QLatin1String( "ttf" ) )
3905  return nullptr;
3906 
3907  const QString fontFamily = name.mid( 6 );
3908 
3909  double angle = 0.0;
3910  QString angleFunc;
3911  if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
3912  {
3913  bool ok;
3914  const double d = angleFunc.toDouble( &ok );
3915  if ( ok )
3916  angle = d;
3917  }
3918 
3919  QPointF offset;
3921 
3922  double scaleFactor = 1.0;
3923  const QString uom = element.attribute( QStringLiteral( "uom" ) );
3924  QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
3925  offset.setX( offset.x() * scaleFactor );
3926  offset.setY( offset.y() * scaleFactor );
3927  size = size * scaleFactor;
3928 
3930  m->setOutputUnit( sldUnitSize );
3931  m->setAngle( angle );
3932  m->setOffset( offset );
3933  return m;
3934 }
3935 
3936 void QgsFontMarkerSymbolLayer::resolveFonts( const QVariantMap &properties, const QgsReadWriteContext &context )
3937 {
3938  const QString fontFamily = properties.value( QStringLiteral( "font" ), DEFAULT_FONTMARKER_FONT ).toString();
3939  const QString processedFamily = QgsApplication::fontManager()->processFontFamilyName( fontFamily );
3940  QString matched;
3941  if ( !QgsFontUtils::fontFamilyMatchOnSystem( processedFamily )
3942  && !QgsApplication::fontManager()->tryToDownloadFontFamily( processedFamily, matched ) )
3943  {
3944  context.pushMessage( QObject::tr( "Font “%1” not available on system" ).arg( processedFamily ) );
3945  }
3946 }
3947 
3949 {
3950  QMap<QString, QgsProperty>::iterator it = mParameters.begin();
3951  for ( ; it != mParameters.end(); ++it )
3952  it.value().prepare( context.renderContext().expressionContext() );
3953 
3955 }
3956 
3957 
3958 QSet<QString> QgsSvgMarkerSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
3959 {
3960  QSet<QString> attrs = QgsMarkerSymbolLayer::usedAttributes( context );
3961 
3962  QMap<QString, QgsProperty>::const_iterator it = mParameters.constBegin();
3963  for ( ; it != mParameters.constEnd(); ++it )
3964  {
3965  attrs.unite( it.value().referencedFields( context.expressionContext(), true ) );
3966  }
3967 
3968  return attrs;
3969 }
3970 
3971 //
3972 // QgsAnimatedMarkerSymbolLayer
3973 //
3974 
3975 QgsAnimatedMarkerSymbolLayer::QgsAnimatedMarkerSymbolLayer( const QString &path, double size, double angle )
3976  : QgsRasterMarkerSymbolLayer( path, size, angle )
3977 {
3978 
3979 }
3980 
3982 
3983 QgsSymbolLayer *QgsAnimatedMarkerSymbolLayer::create( const QVariantMap &properties )
3984 {
3985  QString path;
3988 
3989  if ( properties.contains( QStringLiteral( "imageFile" ) ) )
3990  path = properties[QStringLiteral( "imageFile" )].toString();
3991  if ( properties.contains( QStringLiteral( "size" ) ) )
3992  size = properties[QStringLiteral( "size" )].toDouble();
3993  if ( properties.contains( QStringLiteral( "angle" ) ) )
3994  angle = properties[QStringLiteral( "angle" )].toDouble();
3995 
3996  std::unique_ptr< QgsAnimatedMarkerSymbolLayer > m = std::make_unique< QgsAnimatedMarkerSymbolLayer >( path, size, angle );
3997  m->setFrameRate( properties.value( QStringLiteral( "frameRate" ), QStringLiteral( "10" ) ).toDouble() );
3998 
3999  m->setCommonProperties( properties );
4000  return m.release();
4001 }
4002 
4004 {
4005  return QStringLiteral( "AnimatedMarker" );
4006 }
4007 
4009 {
4010  QVariantMap res = QgsRasterMarkerSymbolLayer::properties();
4011  res.insert( QStringLiteral( "frameRate" ), mFrameRateFps );
4012  return res;
4013 }
4014 
4016 {
4017  std::unique_ptr< QgsAnimatedMarkerSymbolLayer > m = std::make_unique< QgsAnimatedMarkerSymbolLayer >( mPath, mSize, mAngle );
4018  m->setFrameRate( mFrameRateFps );
4019  copyCommonProperties( m.get() );
4020  return m.release();
4021 }
4022 
4024 {
4026 
4027  mPreparedPaths.clear();
4029  {
4031  mStaticPath = true;
4032  }
4033  else
4034  {
4035  mStaticPath = false;
4036  }
4037 }
4038 
4039 QImage QgsAnimatedMarkerSymbolLayer::fetchImage( QgsRenderContext &context, const QString &path, QSize size, bool preserveAspectRatio, double opacity ) const
4040 {
4041  if ( !mStaticPath && !mPreparedPaths.contains( path ) )
4042  {
4044  mPreparedPaths.insert( path );
4045  }
4046 
4047  const long long mapFrameNumber = context.currentFrame();
4048  const int totalFrameCount = QgsApplication::imageCache()->totalFrameCount( path, context.flags() & Qgis::RenderContextFlag::RenderBlocking );
4049  const double markerAnimationDuration = totalFrameCount / mFrameRateFps;
4050 
4051  double animationTimeSeconds = 0;
4052  if ( mapFrameNumber >= 0 && context.frameRate() > 0 )
4053  {
4054  // render is part of an animation, so we base the calculated frame on that
4055  animationTimeSeconds = mapFrameNumber / context.frameRate();
4056  }
4057  else
4058  {
4059  // render is outside of animation, so base the calculated frame on the current epoch
4060  animationTimeSeconds = QDateTime::currentMSecsSinceEpoch() / 1000.0;
4061  }
4062 
4063  const double markerAnimationProgressSeconds = std::fmod( animationTimeSeconds, markerAnimationDuration );
4064  const int movieFrame = static_cast< int >( std::floor( markerAnimationProgressSeconds * mFrameRateFps ) );
4065 
4066  bool cached = false;
4067  return QgsApplication::imageCache()->pathAsImage( path, size, preserveAspectRatio, opacity, cached, context.flags() & Qgis::RenderContextFlag::RenderBlocking, 96, movieFrame );
4068 }
4069 
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
ScaleMethod
Scale methods.
Definition: qgis.h:220
@ ScaleDiameter
Calculate scale by the diameter.
@ ScaleArea
Calculate scale by the area.
MarkerShape
Marker shapes.
Definition: qgis.h:1532
@ Pentagon
Pentagon.
@ EquilateralTriangle
Equilateral triangle.
@ SemiCircle
Semi circle (top half)
@ QuarterCircle
Quarter circle (top left quarter)
@ LeftHalfTriangle
Left half of triangle.
@ ArrowHead
Right facing arrow head (unfilled, lines only)
@ ParallelogramRight
Parallelogram that slants right (since QGIS 3.28)
@ AsteriskFill
A filled asterisk shape (since QGIS 3.18)
@ Octagon
Octagon (since QGIS 3.18)
@ HalfArc
A line-only half arc (since QGIS 3.20)
@ Line
Vertical line.
@ QuarterSquare
Quarter square (top left quarter)
@ Triangle
Triangle.
@ Cross2
Rotated cross (lines only), 'x' shape.
@ Trapezoid
Trapezoid (since QGIS 3.28)
@ ArrowHeadFilled
Right facing filled arrow head.
@ Shield
A shape consisting of a triangle attached to a rectangle (since QGIS 3.28)
@ HalfSquare
Half square (left half)
@ CrossFill
Solid filled cross.
@ Decagon
Decagon (since QGIS 3.28)
@ RoundedSquare
A square with rounded corners (since QGIS 3.28)
@ RightHalfTriangle
Right half of triangle.
@ ThirdCircle
One third circle (top left third)
@ ThirdArc
A line-only one third arc (since QGIS 3.20)
@ SquareWithCorners
A square with diagonal corners (since QGIS 3.18)
@ QuarterArc
A line-only one quarter arc (since QGIS 3.20)
@ DiamondStar
A 4-sided star (since QGIS 3.28)
@ Cross
Cross (lines only)
@ ParallelogramLeft
Parallelogram that slants left (since QGIS 3.28)
@ Heart
Heart (since QGIS 3.28)
@ DiagonalHalfSquare
Diagonal half square (bottom left half)
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
@ Fill
Fill symbol.
QColor valueAsColor(int key, const QgsExpressionContext &context, const QColor &defaultColor=QColor(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a color.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string.
Animated marker symbol layer class.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QgsAnimatedMarkerSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
~QgsAnimatedMarkerSymbolLayer() override
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QImage fetchImage(QgsRenderContext &context, const QString &path, QSize size, bool preserveAspectRatio, double opacity) const override
Fetches the image to render.
QgsAnimatedMarkerSymbolLayer(const QString &path=QString(), double size=DEFAULT_RASTERMARKER_SIZE, double angle=DEFAULT_RASTERMARKER_ANGLE)
Constructor for animated marker symbol layer using the specified source image path.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates an animated marker symbol layer from a string map of properties.
QString layerType() const override
Returns a string that represents this layer type.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
static QgsFontManager * fontManager()
Returns the application font manager, which manages available fonts and font installation for the QGI...
Exports QGIS layers to the DXF format.
Definition: qgsdxfexport.h:65
void writeFilledCircle(const QString &layer, const QColor &color, const QgsPoint &pt, double radius)
Write filled circle (as hatch)
void writeCircle(const QString &layer, const QColor &color, const QgsPoint &pt, double radius, const QString &lineStyleName, double width)
Write circle (as polyline)
void writeLine(const QgsPoint &pt1, const QgsPoint &pt2, const QString &layer, const QString &lineStyleName, const QColor &color, double width=-1)
Write line (as a polyline)
static double mapUnitScaleFactor(double scale, QgsUnitTypes::RenderUnit symbolUnits, QgsUnitTypes::DistanceUnit mapUnits, double mapUnitsPerPixel=1.0)
Returns scale factor for conversion to map units.
QgsUnitTypes::DistanceUnit mapUnits() const
Retrieve map units.
void writePolygon(const QgsRingSequence &polygon, const QString &layer, const QString &hatchPattern, const QColor &color)
Draw dxf filled polygon (HATCH)
double symbologyScale() const
Returns the reference scale for output.
Definition: qgsdxfexport.h:229
void clipValueToMapUnitScale(double &value, const QgsMapUnitScale &scale, double pixelToMMFactor) const
Clips value to scale minimum/maximum.
void writePolyline(const QgsPointSequence &line, const QString &layer, const QString &lineStyleName, const QColor &color, double width=-1)
Draw dxf primitives (LWPOLYLINE)
A paint device for drawing into dxf files.
void setShift(QPointF shift)
void setLayer(const QString &layer)
void setOutputSize(const QRectF &r)
void setDrawingSize(QSizeF size)
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:230
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
Definition: qgsfillsymbol.h:30
static QgsFillSymbol * createSimple(const QVariantMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties.
Filled marker symbol layer, consisting of a shape which is rendered using a QgsFillSymbol.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QColor color() const override
Returns the "representative" color of the symbol layer.
QgsFilledMarkerSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
~QgsFilledMarkerSymbolLayer() override
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
void setColor(const QColor &c) override
Sets the "representative" color for the symbol layer.
QString layerType() const override
Returns a string that represents this layer type.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsFilledMarkerSymbolLayer.
QgsFilledMarkerSymbolLayer(Qgis::MarkerShape shape=Qgis::MarkerShape::Circle, double size=DEFAULT_SIMPLEMARKER_SIZE, double angle=DEFAULT_SIMPLEMARKER_ANGLE, Qgis::ScaleMethod scaleMethod=DEFAULT_SCALE_METHOD)
Constructor for QgsFilledMarkerSymbolLayer.
QString processFontFamilyName(const QString &name) const
Processes a font family name, applying any matching fontFamilyReplacements() to the name.
~QgsFontMarkerSymbolLayer() override
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setStrokeColor(const QColor &color) override
Sets the stroke color for the symbol layer.
QgsFontMarkerSymbolLayer(const QString &fontFamily=DEFAULT_FONTMARKER_FONT, QString chr=DEFAULT_FONTMARKER_CHR, double pointSize=DEFAULT_FONTMARKER_SIZE, const QColor &color=DEFAULT_FONTMARKER_COLOR, double angle=DEFAULT_FONTMARKER_ANGLE)
Constructs a font marker symbol layer.
void setStrokeWidthMapUnitScale(const QgsMapUnitScale &scale)
Sets the stroke width map unit scale.
double strokeWidth() const
Returns the marker's stroke width.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void renderPoint(QPointF point, QgsSymbolRenderContext &context) override
Renders a marker at the specified point.
void setFontStyle(const QString &style)
Sets the font style for the font which will be used to render the point.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QString fontStyle() const
Returns the font style for the associated font which will be used to render the point.
QString fontFamily() const
Returns the font family name for the associated font which will be used to render the point.
void setStrokeWidthUnit(QgsUnitTypes::RenderUnit unit)
Sets the stroke width unit.
QRectF bounds(QPointF point, QgsSymbolRenderContext &context) override
Returns the approximate bounding box of the marker symbol layer, taking into account any data defined...
void writeSldMarker(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Writes the symbol layer definition as a SLD XML element.
void setStrokeWidth(double width)
Set's the marker's stroke width.
static void resolveFonts(const QVariantMap &properties, const QgsReadWriteContext &context)
Resolves fonts from a properties map, raising warnings in the specified context if the required fonts...
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setPenJoinStyle(Qt::PenJoinStyle style)
Sets the stroke join style.
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsFontMarkerSymbolLayer from an SLD XML element.
QgsFontMarkerSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QString layerType() const override
Returns a string that represents this layer type.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsFontMarkerSymbolLayer from a property map (see properties())
static QString translateNamedStyle(const QString &namedStyle)
Returns the localized named style of a font, if such a translation is available.
static bool fontFamilyMatchOnSystem(const QString &family, QString *chosen=nullptr, bool *match=nullptr)
Check whether font family is on system.
static bool updateFontViaStyle(QFont &f, const QString &fontstyle, bool fallback=false)
Updates font with named style and retain all font properties.
QgsWkbTypes::GeometryType type
Definition: qgsgeometry.h:167
QSize originalSize(const QString &path, bool blocking=false) const
Returns the original size (in pixels) of the image at the specified path.
int totalFrameCount(const QString &path, bool blocking=false)
Returns the total frame count of the image at the specified path.
QImage pathAsImage(const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking=false, double targetDpi=96, int frameNumber=-1, bool *isMissing=nullptr)
Returns the specified path rendered as an image.
void prepareAnimation(const QString &path)
Prepares for optimized retrieval of frames for the animation at the given path.
static void overlayColor(QImage &image, const QColor &color)
Overlays a color onto an image.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
double mapUnitsPerPixel() const
Returns the current map units per pixel.
double mapRotation() const
Returns the current map rotation in degrees (clockwise).
Struct for storing maximum and minimum scales for measurements in map units.
Abstract base class for marker symbol layers.
double mSize
Marker size.
QPointF offset() const
Returns the marker's offset, which is the horizontal and vertical displacement which the rendered mar...
double mLineAngle
Line rotation angle (see setLineAngle() for details)
HorizontalAnchorPoint
Symbol horizontal anchor points.
void setAngle(double angle)
Sets the rotation angle for the marker.
Qgis::ScaleMethod scaleMethod() const
Returns the method to use for scaling the marker's size.
QgsUnitTypes::RenderUnit mOffsetUnit
Offset units.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the symbol's size.
void setVerticalAnchorPoint(VerticalAnchorPoint v)
Sets the vertical anchor point for positioning the symbol.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QPointF mOffset
Marker offset.
void setHorizontalAnchorPoint(HorizontalAnchorPoint h)
Sets the horizontal anchor point for positioning the symbol.
QgsMapUnitScale mapUnitScale() const override
void setOffset(QPointF offset)
Sets the marker's offset, which is the horizontal and vertical displacement which the rendered marker...
void setSizeMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the symbol's size.
double size() const
Returns the symbol size.
QgsMapUnitScale mOffsetMapUnitScale
Offset map unit scale.
HorizontalAnchorPoint mHorizontalAnchorPoint
Horizontal anchor point.
static QPointF _rotatedOffset(QPointF offset, double angle)
Adjusts a marker offset to account for rotation.
Qgis::ScaleMethod mScaleMethod
Marker size scaling method.
QgsMapUnitScale mSizeMapUnitScale
Marker size map unit scale.
VerticalAnchorPoint
Symbol vertical anchor points.
void markerOffset(QgsSymbolRenderContext &context, double &offsetX, double &offsetY) const
Calculates the required marker offset, including both the symbol offset and any displacement required...
VerticalAnchorPoint mVerticalAnchorPoint
Vertical anchor point.
QgsUnitTypes::RenderUnit mSizeUnit
Marker size unit.
void setOffsetUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the symbol's offset.
void setOffsetMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for the symbol's offset.
double mAngle
Marker rotation angle, in degrees clockwise from north.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
double angle() const
Returns the rotation angle for the marker, in degrees clockwise from north.
void setMapUnitScale(const QgsMapUnitScale &scale) override
Resolves relative paths into absolute paths and vice versa.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const override
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
static QVariantMap propertyMapToVariantMap(const QMap< QString, QgsProperty > &propertyMap)
Convert a map of QgsProperty to a map of QVariant This is useful to save a map of properties.
static QMap< QString, QgsProperty > variantMapToPropertyMap(const QVariantMap &variantMap)
Convert a map of QVariant to a map of QgsProperty This is useful to restore a map of properties.
static QgsProperty fromValue(const QVariant &value, bool isActive=true)
Returns a new StaticProperty created from the specified value.
Raster marker symbol layer class.
double mFixedAspectRatio
The marker fixed aspect ratio.
QColor color() const override
Returns the "representative" color of the symbol layer.
QRectF bounds(QPointF point, QgsSymbolRenderContext &context) override
Returns the approximate bounding box of the marker symbol layer, taking into account any data defined...
void renderPoint(QPointF point, QgsSymbolRenderContext &context) override
Renders a marker at the specified point.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QgsMapUnitScale mapUnitScale() const override
void copyCommonProperties(QgsRasterMarkerSymbolLayer *other) const
Copies common properties to another layer.
void setOpacity(double opacity)
Set the marker opacity.
QString path() const
Returns the marker raster image path.
double calculateAspectRatio(QgsSymbolRenderContext &context, double scaledSize, bool &hasDataDefinedAspectRatio) const
Calculates the marker aspect ratio between width and height.
QgsRasterMarkerSymbolLayer(const QString &path=QString(), double size=DEFAULT_SVGMARKER_SIZE, double angle=DEFAULT_SVGMARKER_ANGLE, Qgis::ScaleMethod scaleMethod=DEFAULT_SCALE_METHOD)
Constructs raster marker symbol layer with picture from given absolute path to a raster image file.
void setPath(const QString &path)
Set the marker raster image path.
virtual QImage fetchImage(QgsRenderContext &context, const QString &path, QSize size, bool preserveAspectRatio, double opacity) const
Fetches the image to render.
double defaultAspectRatio() const
Returns the default marker aspect ratio between width and height, 0 if not yet calculated.
void setFixedAspectRatio(double ratio)
Set the marker aspect ratio between width and height to be used in rendering, if the value set is low...
void setMapUnitScale(const QgsMapUnitScale &scale) override
void setCommonProperties(const QVariantMap &properties)
Sets common class properties from a properties map.
QgsRasterMarkerSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
bool preservedAspectRatio() const
Returns the preserved aspect ratio value, true if fixed aspect ratio has been lower or equal to 0.
static void resolvePaths(QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving)
Turns relative paths in properties map to absolute when reading and vice versa when writing.
~QgsRasterMarkerSymbolLayer() override
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a raster marker symbol layer from a string map of properties.
double mOpacity
The marker default opacity.
double updateDefaultAspectRatio()
Calculates the default marker aspect ratio between width and height.
double mDefaultAspectRatio
The marker default aspect ratio.
bool setPreservedAspectRatio(bool par)
Set preserved the marker aspect ratio between width and height.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QString layerType() const override
Returns a string that represents this layer type.
double opacity() const
Returns the marker opacity.
The class is used as a container of context for various read/write operations on other objects.
void pushMessage(const QString &message, Qgis::MessageLevel level=Qgis::MessageLevel::Warning) const
Append a message to the context.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
bool forceVectorOutput() const
Returns true if rendering operations should use vector operations instead of any faster raster shortc...
long long currentFrame() const
Returns the current frame number of the map (in frames per second), for maps which are part of an ani...
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
double frameRate() const
Returns the frame rate of the map, for maps which are part of an animation.
QColor selectionColor() const
Returns the color to use when rendering selected features.
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
const QgsPathResolver & pathResolver() const
Returns the path resolver for conversion between relative and absolute paths during rendering operati...
Scoped object for saving and restoring a QPainter object's state.
Abstract base class for simple marker symbol layers.
void renderPoint(QPointF point, QgsSymbolRenderContext &context) override
Renders a marker at the specified point.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void calculateOffsetAndRotation(QgsSymbolRenderContext &context, double scaledSize, bool &hasDataDefinedRotation, QPointF &offset, double &angle) const
Calculates the marker offset and rotation.
Qgis::MarkerShape mShape
Symbol shape.
QPainterPath mPath
Painter path representing shape. If mPolygon is empty then the shape is stored in mPath.
bool shapeToPolygon(Qgis::MarkerShape shape, QPolygonF &polygon) const
Creates a polygon representing the specified shape.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
static QList< Qgis::MarkerShape > availableShapes()
Returns a list of all available shape types.
~QgsSimpleMarkerSymbolLayerBase() override
static bool shapeIsFilled(Qgis::MarkerShape shape)
Returns true if a symbol shape has a fill.
QPolygonF mPolygon
Polygon of points in shape. If polygon is empty then shape is using mPath.
Qgis::MarkerShape shape() const
Returns the shape for the rendered marker symbol.
QRectF bounds(QPointF point, QgsSymbolRenderContext &context) override
Returns the approximate bounding box of the marker symbol layer, taking into account any data defined...
QgsSimpleMarkerSymbolLayerBase(Qgis::MarkerShape shape=Qgis::MarkerShape::Circle, double size=DEFAULT_SIMPLEMARKER_SIZE, double angle=DEFAULT_SIMPLEMARKER_ANGLE, Qgis::ScaleMethod scaleMethod=DEFAULT_SCALE_METHOD)
Constructor for QgsSimpleMarkerSymbolLayerBase.
static QString encodeShape(Qgis::MarkerShape shape)
Encodes a shape to its string representation.
double calculateSize(QgsSymbolRenderContext &context, bool &hasDataDefinedSize) const
Calculates the desired size of the marker, considering data defined size overrides.
static Qgis::MarkerShape decodeShape(const QString &name, bool *ok=nullptr)
Attempts to decode a string representation of a shape name to the corresponding shape.
bool prepareMarkerPath(Qgis::MarkerShape symbol)
Prepares the layer for drawing the specified shape (QPainterPath version)
bool prepareMarkerShape(Qgis::MarkerShape shape)
Prepares the layer for drawing the specified shape (QPolygonF version)
Simple marker symbol layer, consisting of a rendered shape with solid fill color and an stroke.
QPen mSelPen
QPen to use as stroke of selected symbols.
void setStrokeWidthUnit(QgsUnitTypes::RenderUnit u)
Sets the unit for the width of the marker's stroke.
void setColor(const QColor &color) override
Sets the "representative" color for the symbol layer.
QColor mStrokeColor
Stroke color.
QImage mSelCache
Cached image of selected marker, if using cached version.
QImage mCache
Cached image of marker, if using cached version.
QBrush mSelBrush
QBrush to use as fill of selected symbols.
void setFillColor(const QColor &color) override
Sets the fill color for the symbol layer.
Qt::PenJoinStyle penJoinStyle() const
Returns the marker's stroke join style (e.g., miter, bevel, etc).
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void drawMarker(QPainter *p, QgsSymbolRenderContext &context)
Draws the marker shape in the specified painter.
QPen mPen
QPen corresponding to marker's stroke style.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsSimpleMarkerSymbolLayer.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QRectF bounds(QPointF point, QgsSymbolRenderContext &context) override
Returns the approximate bounding box of the marker symbol layer, taking into account any data defined...
void setMapUnitScale(const QgsMapUnitScale &scale) override
QColor color() const override
Returns the "representative" color of the symbol layer.
QgsMapUnitScale mapUnitScale() const override
Qt::PenStyle mStrokeStyle
Stroke style.
QgsSimpleMarkerSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsSimpleMarkerSymbolLayer from an SLD XML element.
~QgsSimpleMarkerSymbolLayer() override
Qt::PenCapStyle mPenCapStyle
Stroke pen cap style.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QString layerType() const override
Returns a string that represents this layer type.
double mStrokeWidth
Stroke width.
void setStrokeWidthMapUnitScale(const QgsMapUnitScale &scale)
Sets the map scale for the width of the marker's stroke.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setStrokeStyle(Qt::PenStyle strokeStyle)
Sets the marker's stroke style (e.g., solid, dashed, etc)
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QColor fillColor() const override
Returns the fill color for the symbol layer.
QColor strokeColor() const override
Returns the marker's stroke color.
QBrush mBrush
QBrush corresponding to marker's fill style.
void setStrokeWidth(double w)
Sets the width of the marker's stroke.
void setStrokeColor(const QColor &color) override
Sets the marker's stroke color.
Qt::PenStyle strokeStyle() const
Returns the marker's stroke style (e.g., solid, dashed, etc)
QgsUnitTypes::RenderUnit mStrokeWidthUnit
Stroke width units.
bool mUsingCache
true if using cached images of markers for drawing.
void setPenCapStyle(Qt::PenCapStyle style)
Sets the marker's stroke cap style (e.g., flat, round, etc).
bool writeDxf(QgsDxfExport &e, double mmMapUnitScaleFactor, const QString &layerName, QgsSymbolRenderContext &context, QPointF shift=QPointF(0.0, 0.0)) const override
write as DXF
void writeSldMarker(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Writes the symbol layer definition as a SLD XML element.
static const int MAXIMUM_CACHE_WIDTH
Maximum width/height of cache image.
QString ogrFeatureStyle(double mmScaleFactor, double mapUnitScaleFactor) const override
bool prepareCache(QgsSymbolRenderContext &context)
Prepares cache image.
QgsMapUnitScale mStrokeWidthMapUnitScale
Stroke width map unit scale.
QgsSimpleMarkerSymbolLayer(Qgis::MarkerShape shape=Qgis::MarkerShape::Circle, double size=DEFAULT_SIMPLEMARKER_SIZE, double angle=DEFAULT_SIMPLEMARKER_ANGLE, Qgis::ScaleMethod scaleMethod=DEFAULT_SCALE_METHOD, const QColor &color=DEFAULT_SIMPLEMARKER_COLOR, const QColor &strokeColor=DEFAULT_SIMPLEMARKER_BORDERCOLOR, Qt::PenJoinStyle penJoinStyle=DEFAULT_SIMPLEMARKER_JOINSTYLE)
Constructor for QgsSimpleMarkerSymbolLayer.
double strokeWidth() const
Returns the width of the marker's stroke.
Qt::PenJoinStyle mPenJoinStyle
Stroke pen join style.
void renderPoint(QPointF point, QgsSymbolRenderContext &context) override
Renders a marker at the specified point.
QSizeF svgViewboxSize(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0, bool blocking=false, const QMap< QString, QString > &parameters=QMap< QString, QString >())
Calculates the viewbox size of a (possibly cached) SVG file.
QPicture svgAsPicture(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool forceVectorOutput=false, double fixedAspectRatio=0, bool blocking=false, const QMap< QString, QString > &parameters=QMap< QString, QString >())
Returns an SVG drawing as a QPicture.
void containsParams(const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasStrokeParam, QColor &defaultStrokeColor, bool &hasStrokeWidthParam, double &defaultStrokeWidth, bool blocking=false) const
Tests if an SVG file contains parameters for fill, stroke color, stroke width.
QImage svgAsImage(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool &fitsInCache, double fixedAspectRatio=0, bool blocking=false, const QMap< QString, QString > &parameters=QMap< QString, QString >())
Returns an SVG drawing as a QImage.
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0, bool blocking=false, const QMap< QString, QString > &parameters=QMap< QString, QString >(), bool *isMissingImage=nullptr)
Gets the SVG content corresponding to the given path.
QgsSvgMarkerSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
QColor fillColor() const override
Returns the fill color for the symbol layer.
QgsMapUnitScale mapUnitScale() const override
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates the symbol.
double mDefaultAspectRatio
The marker default aspect ratio.
QgsUnitTypes::RenderUnit mStrokeWidthUnit
QString layerType() const override
Returns a string that represents this layer type.
void setStrokeWidthMapUnitScale(const QgsMapUnitScale &scale)
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QString path() const
Returns the marker SVG path.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
bool preservedAspectRatio() const
Returns the preserved aspect ratio value, true if fixed aspect ratio has been lower or equal to 0.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void setStrokeWidth(double w)
Definition: