QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
17#include "qgssymbollayerutils.h"
18
19#include "qgsdxfexport.h"
20#include "qgsdxfpaintdevice.h"
21#include "qgsfontutils.h"
22#include "qgsimagecache.h"
23#include "qgsimageoperation.h"
24#include "qgsrendercontext.h"
25#include "qgslogger.h"
26#include "qgssvgcache.h"
27#include "qgsunittypes.h"
28#include "qgssymbol.h"
29#include "qgsfillsymbol.h"
30#include "qgsfontmanager.h"
31#include "qgscolorutils.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
43Q_GUI_EXPORT extern int qt_defaultDpiX();
44Q_GUI_EXPORT extern int qt_defaultDpiY();
45
46static constexpr int MAX_FONT_CHARACTER_SIZE_IN_PIXELS = 500;
47
48static 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::Property::Size );
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, Qgis::RenderUnit::Millimeters ), 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, Qgis::RenderUnit::Millimeters ), 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
895double QgsSimpleMarkerSymbolLayerBase::calculateSize( QgsSymbolRenderContext &context, bool &hasDataDefinedSize ) const
896{
897 double scaledSize = mSize;
898
900 bool ok = true;
901 if ( hasDataDefinedSize )
902 {
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
923void 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;
936 {
939
940 // If the expression evaluation was not successful, fallback to static value
941 if ( !ok )
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() == Qgis::GeometryType::Point )
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
976QgsSimpleMarkerSymbolLayer::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{
995
996 if ( props.contains( QStringLiteral( "name" ) ) )
997 {
998 shape = decodeShape( props[QStringLiteral( "name" )].toString() );
999 }
1000 if ( props.contains( QStringLiteral( "color" ) ) )
1001 color = QgsColorUtils::colorFromString( props[QStringLiteral( "color" )].toString() );
1002 if ( props.contains( QStringLiteral( "color_border" ) ) )
1003 {
1004 //pre 2.5 projects use "color_border"
1005 strokeColor = QgsColorUtils::colorFromString( props[QStringLiteral( "color_border" )].toString() );
1006 }
1007 else if ( props.contains( QStringLiteral( "outline_color" ) ) )
1008 {
1009 strokeColor = QgsColorUtils::colorFromString( props[QStringLiteral( "outline_color" )].toString() );
1010 }
1011 else if ( props.contains( QStringLiteral( "line_color" ) ) )
1012 {
1013 strokeColor = QgsColorUtils::colorFromString( 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
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::Property::Size );
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 );
1161 const double deviceRatio = context.renderContext().devicePixelRatio();
1163 {
1164 // rendering for symbol previews -- a size in meters in map units can't be calculated, so treat the size as millimeters
1165 // and clamp it to a reasonable range. It's the best we can do in this situation!
1166 scaledSize = std::min( std::max( context.renderContext().convertToPainterUnits( mSize, Qgis::RenderUnit::Millimeters ), 3.0 ), 100.0 );
1167 }
1168
1169 // take into account angle (which is not data-defined otherwise cache wouldn't be used)
1170 if ( !qgsDoubleNear( mAngle, 0.0 ) )
1171 {
1172 scaledSize = ( std::abs( std::sin( mAngle * M_PI / 180 ) ) + std::abs( std::cos( mAngle * M_PI / 180 ) ) ) * scaledSize;
1173 }
1174 // calculate necessary image size for the cache
1175 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
1176 const int imageSize = ( static_cast< int >( scaledSize ) + pw ) / 2 * 2 + 1; // make image width, height odd; account for pen width
1177 const double center = imageSize / 2.0;
1178 if ( imageSize * deviceRatio > MAXIMUM_CACHE_WIDTH )
1179 {
1180 return false;
1181 }
1182
1183 mCache = QImage( QSize( imageSize * deviceRatio,
1184 imageSize * deviceRatio ), QImage::Format_ARGB32_Premultiplied );
1185 mCache.setDevicePixelRatio( context.renderContext().devicePixelRatio() );
1186 mCache.setDotsPerMeterX( std::round( context.renderContext().scaleFactor() * 1000 ) );
1187 mCache.setDotsPerMeterY( std::round( context.renderContext().scaleFactor() * 1000 ) );
1188 mCache.fill( 0 );
1189
1190 const bool needsBrush = shapeIsFilled( mShape );
1191
1192 QPainter p;
1193 p.begin( &mCache );
1194 p.setRenderHint( QPainter::Antialiasing );
1195 p.setBrush( needsBrush ? mBrush : Qt::NoBrush );
1196 p.setPen( mPen );
1197 p.translate( QPointF( center, center ) );
1198 drawMarker( &p, context );
1199 p.end();
1200
1201 // Construct the selected version of the Cache
1202
1203 const QColor selColor = context.renderContext().selectionColor();
1204
1205 mSelCache = QImage( QSize( imageSize, imageSize ), QImage::Format_ARGB32_Premultiplied );
1206 mSelCache.fill( 0 );
1207
1208 p.begin( &mSelCache );
1209 p.setRenderHint( QPainter::Antialiasing );
1210 p.setBrush( needsBrush ? mSelBrush : Qt::NoBrush );
1211 p.setPen( mSelPen );
1212 p.translate( QPointF( center, center ) );
1213 drawMarker( &p, context );
1214 p.end();
1215
1216 // Check that the selected version is different. If not, then re-render,
1217 // filling the background with the selection color and using the normal
1218 // colors for the symbol .. could be ugly!
1219
1220 if ( mSelCache == mCache )
1221 {
1222 p.begin( &mSelCache );
1223 p.setRenderHint( QPainter::Antialiasing );
1224 p.fillRect( 0, 0, imageSize, imageSize, selColor );
1225 p.setBrush( needsBrush ? mBrush : Qt::NoBrush );
1226 p.setPen( mPen );
1227 p.translate( QPointF( center, center ) );
1228 drawMarker( &p, context );
1229 p.end();
1230 }
1231
1232 return true;
1233}
1234
1235void QgsSimpleMarkerSymbolLayer::draw( QgsSymbolRenderContext &context, Qgis::MarkerShape shape, const QPolygonF &polygon, const QPainterPath &path )
1236{
1237 //making changes here? Don't forget to also update ::bounds if the changes affect the bounding box
1238 //of the rendered point!
1239
1240 QPainter *p = context.renderContext().painter();
1241 if ( !p )
1242 {
1243 return;
1244 }
1245
1246 QColor brushColor = mColor;
1247 brushColor.setAlphaF( brushColor.alphaF() * context.opacity() );
1248 mBrush.setColor( brushColor );
1249
1250 QColor penColor = mStrokeColor;
1251 penColor.setAlphaF( penColor.alphaF() * context.opacity() );
1252 mPen.setColor( penColor );
1253
1254 bool ok = true;
1256 {
1259 if ( ok )
1260 {
1261 c.setAlphaF( c.alphaF() * context.opacity() );
1262 mBrush.setColor( c );
1263 }
1264 }
1266 {
1269 if ( ok )
1270 {
1271 c.setAlphaF( c.alphaF() * context.opacity() );
1272 mPen.setColor( c );
1273 mSelPen.setColor( c );
1274 }
1275 }
1277 {
1280 if ( ok )
1281 {
1284 }
1285 }
1287 {
1290 if ( ok )
1291 {
1294 }
1295 }
1297 {
1300 if ( ok )
1301 {
1302 mPen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
1303 mSelPen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
1304 }
1305 }
1307 {
1310 if ( ok )
1311 {
1312 mPen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( style ) );
1313 mSelPen.setCapStyle( QgsSymbolLayerUtils::decodePenCapStyle( style ) );
1314 }
1315 }
1316
1317 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1318 if ( shapeIsFilled( shape ) )
1319 {
1320 p->setBrush( useSelectedColor ? mSelBrush : mBrush );
1321 }
1322 else
1323 {
1324 p->setBrush( Qt::NoBrush );
1325 }
1326 p->setPen( useSelectedColor ? mSelPen : mPen );
1327
1328 if ( !polygon.isEmpty() )
1329 p->drawPolygon( polygon );
1330 else
1331 p->drawPath( path );
1332}
1333
1335{
1336 //making changes here? Don't forget to also update ::bounds if the changes affect the bounding box
1337 //of the rendered point!
1338
1339 QPainter *p = context.renderContext().painter();
1340 if ( !p )
1341 {
1342 return;
1343 }
1344
1345 if ( mUsingCache && qgsDoubleNear( mCachedOpacity, context.opacity() ) )
1346 {
1347 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1348 const QImage &img = useSelectedColor ? mSelCache : mCache;
1349 const double s = img.width() / img.devicePixelRatioF();
1350
1351 bool hasDataDefinedSize = false;
1352 const double scaledSize = calculateSize( context, hasDataDefinedSize );
1353
1354 bool hasDataDefinedRotation = false;
1355 QPointF offset;
1356 double angle = 0;
1357 calculateOffsetAndRotation( context, scaledSize, hasDataDefinedRotation, offset, angle );
1358
1359 p->drawImage( QRectF( point.x() - s / 2.0 + offset.x(),
1360 point.y() - s / 2.0 + offset.y(),
1361 s, s ), img );
1362 }
1363 else
1364 {
1366 }
1367}
1368
1370{
1371 QVariantMap map;
1372 map[QStringLiteral( "name" )] = encodeShape( mShape );
1373 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mColor );
1374 map[QStringLiteral( "outline_color" )] = QgsColorUtils::colorToString( mStrokeColor );
1375 map[QStringLiteral( "size" )] = QString::number( mSize );
1376 map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( mSizeUnit );
1377 map[QStringLiteral( "size_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
1378 map[QStringLiteral( "angle" )] = QString::number( mAngle );
1379 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1380 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1381 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1382 map[QStringLiteral( "scale_method" )] = QgsSymbolLayerUtils::encodeScaleMethod( mScaleMethod );
1383 map[QStringLiteral( "outline_style" )] = QgsSymbolLayerUtils::encodePenStyle( mStrokeStyle );
1384 map[QStringLiteral( "outline_width" )] = QString::number( mStrokeWidth );
1385 map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( mStrokeWidthUnit );
1386 map[QStringLiteral( "outline_width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale );
1387 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
1388 map[QStringLiteral( "cap_style" )] = QgsSymbolLayerUtils::encodePenCapStyle( mPenCapStyle );
1389 map[QStringLiteral( "horizontal_anchor_point" )] = QString::number( mHorizontalAnchorPoint );
1390 map[QStringLiteral( "vertical_anchor_point" )] = QString::number( mVerticalAnchorPoint );
1391 return map;
1392}
1393
1395{
1397 m->setOffset( mOffset );
1398 m->setSizeUnit( mSizeUnit );
1410 copyPaintEffect( m );
1411 return m;
1412}
1413
1414void QgsSimpleMarkerSymbolLayer::writeSldMarker( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
1415{
1416 // <Graphic>
1417 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
1418 element.appendChild( graphicElem );
1419
1421 const double size = QgsSymbolLayerUtils::rescaleUom( mSize, mSizeUnit, props );
1423
1424 // <Rotation>
1425 QString angleFunc;
1426
1428 {
1430 }
1431 else
1432 {
1433 bool ok;
1434 const double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
1435 if ( !ok )
1436 {
1437 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
1438 }
1439 else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
1440 {
1441 angleFunc = QString::number( angle + mAngle );
1442 }
1443 }
1444
1445 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
1446
1447 // <Displacement>
1448 const QPointF offset = QgsSymbolLayerUtils::rescaleUom( mOffset, mOffsetUnit, props );
1450}
1451
1452QString QgsSimpleMarkerSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
1453{
1454 Q_UNUSED( mmScaleFactor )
1455 Q_UNUSED( mapUnitScaleFactor )
1456#if 0
1457 QString ogrType = "3"; //default is circle
1458 if ( mName == "square" )
1459 {
1460 ogrType = "5";
1461 }
1462 else if ( mName == "triangle" )
1463 {
1464 ogrType = "7";
1465 }
1466 else if ( mName == "star" )
1467 {
1468 ogrType = "9";
1469 }
1470 else if ( mName == "circle" )
1471 {
1472 ogrType = "3";
1473 }
1474 else if ( mName == "cross" )
1475 {
1476 ogrType = "0";
1477 }
1478 else if ( mName == "x" || mName == "cross2" )
1479 {
1480 ogrType = "1";
1481 }
1482 else if ( mName == "line" )
1483 {
1484 ogrType = "10";
1485 }
1486
1487 QString ogrString;
1488 ogrString.append( "SYMBOL(" );
1489 ogrString.append( "id:" );
1490 ogrString.append( '\"' );
1491 ogrString.append( "ogr-sym-" );
1492 ogrString.append( ogrType );
1493 ogrString.append( '\"' );
1494 ogrString.append( ",c:" );
1495 ogrString.append( mColor.name() );
1496 ogrString.append( ",o:" );
1497 ogrString.append( mStrokeColor.name() );
1498 ogrString.append( QString( ",s:%1mm" ).arg( mSize ) );
1499 ogrString.append( ')' );
1500 return ogrString;
1501#endif //0
1502
1503 QString ogrString;
1504 ogrString.append( "PEN(" );
1505 ogrString.append( "c:" );
1506 ogrString.append( mColor.name() );
1507 ogrString.append( ",w:" );
1508 ogrString.append( QString::number( mSize ) );
1509 ogrString.append( "mm" );
1510 ogrString.append( ")" );
1511 return ogrString;
1512}
1513
1515{
1516 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
1517
1518 QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
1519 if ( graphicElem.isNull() )
1520 return nullptr;
1521
1522 QString name = QStringLiteral( "square" );
1523 QColor color, strokeColor;
1524 double strokeWidth, size;
1525 Qt::PenStyle strokeStyle;
1526
1528 return nullptr;
1529
1530 double angle = 0.0;
1531 QString angleFunc;
1532 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
1533 {
1534 bool ok;
1535 const double d = angleFunc.toDouble( &ok );
1536 if ( ok )
1537 angle = d;
1538 }
1539
1540 QPointF offset;
1542
1543 const Qgis::MarkerShape shape = decodeShape( name );
1544
1545 double scaleFactor = 1.0;
1546 const QString uom = element.attribute( QStringLiteral( "uom" ) );
1547 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
1548 size = size * scaleFactor;
1549 offset.setX( offset.x() * scaleFactor );
1550 offset.setY( offset.y() * scaleFactor );
1551
1553 m->setOutputUnit( sldUnitSize );
1554 m->setColor( color );
1556 m->setAngle( angle );
1557 m->setOffset( offset );
1560 return m;
1561}
1562
1564{
1565 Q_UNUSED( context )
1566
1567 if ( mPolygon.count() != 0 )
1568 {
1569 p->drawPolygon( mPolygon );
1570 }
1571 else
1572 {
1573 p->drawPath( mPath );
1574 }
1575}
1576
1577bool QgsSimpleMarkerSymbolLayer::writeDxf( QgsDxfExport &e, double mmMapUnitScaleFactor, const QString &layerName, QgsSymbolRenderContext &context, QPointF shift ) const
1578{
1579 //data defined size?
1580 double size = mSize;
1581
1582 const bool hasDataDefinedSize = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Size );
1583
1584 //data defined size
1585 bool ok = true;
1586 if ( hasDataDefinedSize )
1587 {
1589
1590 if ( ok )
1591 {
1592 switch ( mScaleMethod )
1593 {
1595 size = std::sqrt( size );
1596 break;
1598 break;
1599 }
1600 }
1601 }
1602
1604 {
1605 size *= mmMapUnitScaleFactor;
1606 }
1607
1609 {
1611 }
1612 const double halfSize = size / 2.0;
1613
1614 //strokeWidth
1615 double strokeWidth = mStrokeWidth;
1616
1618 {
1621 }
1624 {
1626 }
1627
1628 //color
1629 QColor pc = mPen.color();
1630 QColor bc = mBrush.color();
1632 {
1635 }
1637 {
1640 }
1641
1642 //offset
1643 double offsetX = 0;
1644 double offsetY = 0;
1645 markerOffset( context, offsetX, offsetY );
1646 offsetX *= context.renderContext().mapToPixel().mapUnitsPerPixel();
1647 offsetY *= context.renderContext().mapToPixel().mapUnitsPerPixel();
1648
1649
1650 QPointF off( offsetX, offsetY );
1651
1652 //angle
1653 double angle = mAngle + mLineAngle;
1655 {
1658 }
1659
1662 {
1664 const QString shapeName = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::Name, context.renderContext().expressionContext(), QString(), &ok );
1665 if ( ok )
1666 {
1667 shape = decodeShape( shapeName, &ok );
1668 if ( !ok )
1669 shape = mShape;
1670 }
1671 }
1672
1673 if ( angle )
1674 off = _rotatedOffset( off, angle );
1675
1677
1678 QTransform t;
1679 t.translate( shift.x() + off.x(), shift.y() - off.y() );
1680
1681 if ( !qgsDoubleNear( angle, 0.0 ) )
1682 t.rotate( -angle );
1683
1684 QPolygonF polygon;
1685 if ( shapeToPolygon( shape, polygon ) )
1686 {
1687 t.scale( halfSize, -halfSize );
1688
1689 polygon = t.map( polygon );
1690
1692 p.reserve( polygon.size() );
1693 for ( int i = 0; i < polygon.size(); i++ )
1694 {
1695 p << QgsPoint( polygon[i] );
1696 }
1697
1698 if ( mBrush.style() != Qt::NoBrush )
1699 e.writePolygon( QgsRingSequence() << p, layerName, QStringLiteral( "SOLID" ), bc );
1700 if ( mPen.style() != Qt::NoPen )
1701 e.writePolyline( p, layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1702 }
1703 else if ( shape == Qgis::MarkerShape::Circle )
1704 {
1705 shift += QPointF( off.x(), -off.y() );
1706 if ( mBrush.style() != Qt::NoBrush )
1707 e.writeFilledCircle( layerName, bc, QgsPoint( shift ), halfSize );
1708 if ( mPen.style() != Qt::NoPen )
1709 e.writeCircle( layerName, pc, QgsPoint( shift ), halfSize, QStringLiteral( "CONTINUOUS" ), strokeWidth );
1710 }
1711 else if ( shape == Qgis::MarkerShape::Line )
1712 {
1713 const QPointF pt1 = t.map( QPointF( 0, -halfSize ) );
1714 const QPointF pt2 = t.map( QPointF( 0, halfSize ) );
1715
1716 if ( mPen.style() != Qt::NoPen )
1717 e.writeLine( QgsPoint( pt1 ), QgsPoint( pt2 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1718 }
1719 else if ( shape == Qgis::MarkerShape::Cross )
1720 {
1721 if ( mPen.style() != Qt::NoPen )
1722 {
1723 const QPointF pt1 = t.map( QPointF( -halfSize, 0 ) );
1724 const QPointF pt2 = t.map( QPointF( halfSize, 0 ) );
1725 const QPointF pt3 = t.map( QPointF( 0, -halfSize ) );
1726 const QPointF pt4 = t.map( QPointF( 0, halfSize ) );
1727
1728 e.writeLine( QgsPoint( pt1 ), QgsPoint( pt2 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1729 e.writeLine( QgsPoint( pt3 ), QgsPoint( pt4 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1730 }
1731 }
1732 else if ( shape == Qgis::MarkerShape::Cross2 )
1733 {
1734 if ( mPen.style() != Qt::NoPen )
1735 {
1736 const QPointF pt1 = t.map( QPointF( -halfSize, -halfSize ) );
1737 const QPointF pt2 = t.map( QPointF( halfSize, halfSize ) );
1738 const QPointF pt3 = t.map( QPointF( halfSize, -halfSize ) );
1739 const QPointF pt4 = t.map( QPointF( -halfSize, halfSize ) );
1740
1741 e.writeLine( QgsPoint( pt1 ), QgsPoint( pt2 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1742 e.writeLine( QgsPoint( pt3 ), QgsPoint( pt4 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1743 }
1744 }
1745 else if ( shape == Qgis::MarkerShape::ArrowHead )
1746 {
1747 if ( mPen.style() != Qt::NoPen )
1748 {
1749 const QPointF pt1 = t.map( QPointF( -halfSize, halfSize ) );
1750 const QPointF pt2 = t.map( QPointF( 0, 0 ) );
1751 const QPointF pt3 = t.map( QPointF( -halfSize, -halfSize ) );
1752
1753 e.writeLine( QgsPoint( pt1 ), QgsPoint( pt2 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1754 e.writeLine( QgsPoint( pt3 ), QgsPoint( pt2 ), layerName, QStringLiteral( "CONTINUOUS" ), pc, strokeWidth );
1755 }
1756 }
1757 else
1758 {
1759 QgsDebugError( QStringLiteral( "Unsupported dxf marker name %1" ).arg( encodeShape( shape ) ) );
1760 return false;
1761 }
1762
1763 return true;
1764}
1765
1766
1768{
1770 mStrokeWidthUnit = unit;
1771}
1772
1774{
1776 {
1777 return mStrokeWidthUnit;
1778 }
1780}
1781
1783{
1786}
1787
1789{
1791 {
1793 }
1794 return QgsMapUnitScale();
1795}
1796
1798{
1802}
1803
1805{
1806 QRectF symbolBounds = QgsSimpleMarkerSymbolLayerBase::bounds( point, context );
1807
1808 // need to account for stroke width
1809 double penWidth = mStrokeWidth;
1810 bool ok = true;
1812 {
1815 if ( ok )
1816 {
1817 penWidth = strokeWidth;
1818 }
1819 }
1822 {
1825 if ( ok && strokeStyle == QLatin1String( "no" ) )
1826 {
1827 penWidth = 0.0;
1828 }
1829 }
1830 else if ( mStrokeStyle == Qt::NoPen )
1831 penWidth = 0;
1832
1833 //antialiasing, add 1 pixel
1834 penWidth += 1;
1835
1836 //extend bounds by pen width / 2.0
1837 symbolBounds.adjust( -penWidth / 2.0, -penWidth / 2.0,
1838 penWidth / 2.0, penWidth / 2.0 );
1839
1840 return symbolBounds;
1841}
1842
1843void QgsSimpleMarkerSymbolLayer::setColor( const QColor &color )
1844{
1845 if ( shapeIsFilled( mShape ) )
1846 {
1848 }
1849 else
1850 {
1852 }
1853}
1854
1856{
1857 if ( shapeIsFilled( mShape ) )
1858 {
1859 return fillColor();
1860 }
1861 else
1862 {
1863 return strokeColor();
1864 }
1865}
1866
1867
1868
1869
1870//
1871// QgsFilledMarkerSymbolLayer
1872//
1873
1875 : QgsSimpleMarkerSymbolLayerBase( shape, size, angle, scaleMethod )
1876{
1877 mFill.reset( static_cast<QgsFillSymbol *>( QgsFillSymbol::createSimple( QVariantMap() ) ) );
1878}
1879
1881
1883{
1884 QString name = DEFAULT_SIMPLEMARKER_NAME;
1888
1889 if ( props.contains( QStringLiteral( "name" ) ) )
1890 name = props[QStringLiteral( "name" )].toString();
1891 if ( props.contains( QStringLiteral( "size" ) ) )
1892 size = props[QStringLiteral( "size" )].toDouble();
1893 if ( props.contains( QStringLiteral( "angle" ) ) )
1894 angle = props[QStringLiteral( "angle" )].toDouble();
1895 if ( props.contains( QStringLiteral( "scale_method" ) ) )
1896 scaleMethod = QgsSymbolLayerUtils::decodeScaleMethod( props[QStringLiteral( "scale_method" )].toString() );
1897
1899 if ( props.contains( QStringLiteral( "offset" ) ) )
1900 m->setOffset( QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() ) );
1901 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
1902 m->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
1903 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1904 m->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1905 if ( props.contains( QStringLiteral( "size_unit" ) ) )
1906 m->setSizeUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "size_unit" )].toString() ) );
1907 if ( props.contains( QStringLiteral( "size_map_unit_scale" ) ) )
1908 m->setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "size_map_unit_scale" )].toString() ) );
1909 if ( props.contains( QStringLiteral( "horizontal_anchor_point" ) ) )
1910 {
1911 m->setHorizontalAnchorPoint( QgsMarkerSymbolLayer::HorizontalAnchorPoint( props[ QStringLiteral( "horizontal_anchor_point" )].toInt() ) );
1912 }
1913 if ( props.contains( QStringLiteral( "vertical_anchor_point" ) ) )
1914 {
1915 m->setVerticalAnchorPoint( QgsMarkerSymbolLayer::VerticalAnchorPoint( props[ QStringLiteral( "vertical_anchor_point" )].toInt() ) );
1916 }
1917
1919
1921
1922 return m;
1923}
1924
1926{
1927 return QStringLiteral( "FilledMarker" );
1928}
1929
1931{
1932 if ( mFill )
1933 {
1934 mFill->startRender( context.renderContext(), context.fields() );
1935 }
1936
1938}
1939
1941{
1942 if ( mFill )
1943 {
1944 mFill->stopRender( context.renderContext() );
1945 }
1946}
1947
1949{
1950 QVariantMap map;
1951 map[QStringLiteral( "name" )] = encodeShape( mShape );
1952 map[QStringLiteral( "size" )] = QString::number( mSize );
1953 map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( mSizeUnit );
1954 map[QStringLiteral( "size_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
1955 map[QStringLiteral( "angle" )] = QString::number( mAngle );
1956 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1957 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1958 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1959 map[QStringLiteral( "scale_method" )] = QgsSymbolLayerUtils::encodeScaleMethod( mScaleMethod );
1960 map[QStringLiteral( "horizontal_anchor_point" )] = QString::number( mHorizontalAnchorPoint );
1961 map[QStringLiteral( "vertical_anchor_point" )] = QString::number( mVerticalAnchorPoint );
1962
1963 if ( mFill )
1964 {
1965 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mFill->color() );
1966 }
1967 return map;
1968}
1969
1971{
1973 copyPaintEffect( m );
1975 m->setSubSymbol( mFill->clone() );
1976 return m;
1977}
1978
1980{
1981 return mFill.get();
1982}
1983
1985{
1986 if ( symbol && symbol->type() == Qgis::SymbolType::Fill )
1987 {
1988 mFill.reset( static_cast<QgsFillSymbol *>( symbol ) );
1989 return true;
1990 }
1991 else
1992 {
1993 delete symbol;
1994 return false;
1995 }
1996}
1997
1999{
2000 if ( mFill )
2001 {
2002 return QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFill.get(), context );
2003 }
2004 return 0;
2005}
2006
2008{
2009 QSet<QString> attr = QgsSimpleMarkerSymbolLayerBase::usedAttributes( context );
2010 if ( mFill )
2011 attr.unite( mFill->usedAttributes( context ) );
2012 return attr;
2013}
2014
2016{
2018 return true;
2019 if ( mFill && mFill->hasDataDefinedProperties() )
2020 return true;
2021 return false;
2022}
2023
2025{
2026 mColor = c;
2027 if ( mFill )
2028 mFill->setColor( c );
2029}
2030
2032{
2033 return mFill ? mFill->color() : mColor;
2034}
2035
2037{
2040 || ( mFill && mFill->usesMapUnits() );
2041}
2042
2044{
2046 if ( mFill )
2047 mFill->setOutputUnit( unit );
2048}
2049
2050void QgsFilledMarkerSymbolLayer::draw( QgsSymbolRenderContext &context, Qgis::MarkerShape shape, const QPolygonF &polygon, const QPainterPath &path )
2051{
2052 //making changes here? Don't forget to also update ::bounds if the changes affect the bounding box
2053 //of the rendered point!
2054
2055 QPainter *p = context.renderContext().painter();
2056 if ( !p )
2057 {
2058 return;
2059 }
2060
2061 const double prevOpacity = mFill->opacity();
2062 mFill->setOpacity( mFill->opacity() * context.opacity() );
2063
2064 if ( shapeIsFilled( shape ) )
2065 {
2066 p->setBrush( Qt::red );
2067 }
2068 else
2069 {
2070 p->setBrush( Qt::NoBrush );
2071 }
2072 p->setPen( Qt::black );
2073
2074 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
2076
2077 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2078 if ( !polygon.isEmpty() )
2079 {
2080 mFill->renderPolygon( polygon, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
2081 }
2082 else
2083 {
2084 const QPolygonF poly = path.toFillPolygon();
2085 mFill->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext(), -1, useSelectedColor );
2086 }
2087
2089
2090 mFill->setOpacity( prevOpacity );
2091}
2092
2093
2095
2096
2097QgsSvgMarkerSymbolLayer::QgsSvgMarkerSymbolLayer( const QString &path, double size, double angle, Qgis::ScaleMethod scaleMethod )
2098{
2099 mSize = size;
2100 mAngle = angle;
2101 mOffset = QPointF( 0, 0 );
2103 mStrokeWidth = 0.2;
2105 mColor = QColor( 35, 35, 35 );
2106 mStrokeColor = QColor( 35, 35, 35 );
2107 setPath( path );
2108}
2109
2111
2113{
2114 QString name;
2118
2119 if ( props.contains( QStringLiteral( "name" ) ) )
2120 name = props[QStringLiteral( "name" )].toString();
2121 if ( props.contains( QStringLiteral( "size" ) ) )
2122 size = props[QStringLiteral( "size" )].toDouble();
2123 if ( props.contains( QStringLiteral( "angle" ) ) )
2124 angle = props[QStringLiteral( "angle" )].toDouble();
2125 if ( props.contains( QStringLiteral( "scale_method" ) ) )
2126 scaleMethod = QgsSymbolLayerUtils::decodeScaleMethod( props[QStringLiteral( "scale_method" )].toString() );
2127
2129
2130 if ( props.contains( QStringLiteral( "size_unit" ) ) )
2131 m->setSizeUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "size_unit" )].toString() ) );
2132 if ( props.contains( QStringLiteral( "size_map_unit_scale" ) ) )
2133 m->setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "size_map_unit_scale" )].toString() ) );
2134 if ( props.contains( QStringLiteral( "fixedAspectRatio" ) ) )
2135 m->setFixedAspectRatio( props[QStringLiteral( "fixedAspectRatio" )].toDouble() );
2136 if ( props.contains( QStringLiteral( "offset" ) ) )
2137 m->setOffset( QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() ) );
2138 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
2139 m->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
2140 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
2141 m->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
2142 if ( props.contains( QStringLiteral( "fill" ) ) )
2143 {
2144 //pre 2.5 projects used "fill"
2145 m->setFillColor( QgsColorUtils::colorFromString( props[QStringLiteral( "fill" )].toString() ) );
2146 }
2147 else if ( props.contains( QStringLiteral( "color" ) ) )
2148 {
2149 m->setFillColor( QgsColorUtils::colorFromString( props[QStringLiteral( "color" )].toString() ) );
2150 }
2151 if ( props.contains( QStringLiteral( "outline" ) ) )
2152 {
2153 //pre 2.5 projects used "outline"
2154 m->setStrokeColor( QgsColorUtils::colorFromString( props[QStringLiteral( "outline" )].toString() ) );
2155 }
2156 else if ( props.contains( QStringLiteral( "outline_color" ) ) )
2157 {
2158 m->setStrokeColor( QgsColorUtils::colorFromString( props[QStringLiteral( "outline_color" )].toString() ) );
2159 }
2160 else if ( props.contains( QStringLiteral( "line_color" ) ) )
2161 {
2162 m->setStrokeColor( QgsColorUtils::colorFromString( props[QStringLiteral( "line_color" )].toString() ) );
2163 }
2164
2165 if ( props.contains( QStringLiteral( "outline-width" ) ) )
2166 {
2167 //pre 2.5 projects used "outline-width"
2168 m->setStrokeWidth( props[QStringLiteral( "outline-width" )].toDouble() );
2169 }
2170 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
2171 {
2172 m->setStrokeWidth( props[QStringLiteral( "outline_width" )].toDouble() );
2173 }
2174 else if ( props.contains( QStringLiteral( "line_width" ) ) )
2175 {
2176 m->setStrokeWidth( props[QStringLiteral( "line_width" )].toDouble() );
2177 }
2178
2179 if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
2180 {
2181 m->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
2182 }
2183 else if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
2184 {
2185 m->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
2186 }
2187 if ( props.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2188 m->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2189
2190 if ( props.contains( QStringLiteral( "horizontal_anchor_point" ) ) )
2191 {
2192 m->setHorizontalAnchorPoint( QgsMarkerSymbolLayer::HorizontalAnchorPoint( props[ QStringLiteral( "horizontal_anchor_point" )].toInt() ) );
2193 }
2194 if ( props.contains( QStringLiteral( "vertical_anchor_point" ) ) )
2195 {
2196 m->setVerticalAnchorPoint( QgsMarkerSymbolLayer::VerticalAnchorPoint( props[ QStringLiteral( "vertical_anchor_point" )].toInt() ) );
2197 }
2198
2200
2202
2203 if ( props.contains( QStringLiteral( "parameters" ) ) )
2204 {
2205 const QVariantMap parameters = props[QStringLiteral( "parameters" )].toMap();
2207 }
2208
2209 return m;
2210}
2211
2212void QgsSvgMarkerSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
2213{
2214 const QVariantMap::iterator it = properties.find( QStringLiteral( "name" ) );
2215 if ( it != properties.end() )
2216 {
2217 if ( saving )
2218 {
2219 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
2220 }
2221 else
2222 {
2223 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
2224 }
2225 }
2226}
2227
2228void QgsSvgMarkerSymbolLayer::setPath( const QString &path )
2229{
2231 mHasFillParam = false;
2232 mPath = path;
2233 QColor defaultFillColor, defaultStrokeColor;
2234 double strokeWidth, fillOpacity, strokeOpacity;
2235 bool hasFillOpacityParam = false, hasStrokeParam = false, hasStrokeWidthParam = false, hasStrokeOpacityParam = false;
2236 bool hasDefaultFillColor = false, hasDefaultFillOpacity = false, hasDefaultStrokeColor = false, hasDefaultStrokeWidth = false, hasDefaultStrokeOpacity = false;
2237 QgsApplication::svgCache()->containsParams( path, mHasFillParam, hasDefaultFillColor, defaultFillColor,
2238 hasFillOpacityParam, hasDefaultFillOpacity, fillOpacity,
2239 hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
2240 hasStrokeWidthParam, hasDefaultStrokeWidth, strokeWidth,
2241 hasStrokeOpacityParam, hasDefaultStrokeOpacity, strokeOpacity );
2242
2243 const double newFillOpacity = hasFillOpacityParam ? fillColor().alphaF() : 1.0;
2244 const double newStrokeOpacity = hasStrokeOpacityParam ? strokeColor().alphaF() : 1.0;
2245
2246 if ( hasDefaultFillColor )
2247 {
2248 defaultFillColor.setAlphaF( newFillOpacity );
2249 setFillColor( defaultFillColor );
2250 }
2251 if ( hasDefaultFillOpacity )
2252 {
2253 QColor c = fillColor();
2254 c.setAlphaF( fillOpacity );
2255 setFillColor( c );
2256 }
2257 if ( hasDefaultStrokeColor )
2258 {
2259 defaultStrokeColor.setAlphaF( newStrokeOpacity );
2260 setStrokeColor( defaultStrokeColor );
2261 }
2262 if ( hasDefaultStrokeWidth )
2263 {
2265 }
2266 if ( hasDefaultStrokeOpacity )
2267 {
2268 QColor c = strokeColor();
2269 c.setAlphaF( strokeOpacity );
2270 setStrokeColor( c );
2271 }
2272
2274}
2275
2277{
2278 if ( mDefaultAspectRatio == 0.0 )
2279 {
2280 //size
2281 const double size = mSize;
2282 //assume 88 dpi as standard value
2283 const double widthScaleFactor = 3.465;
2284 const QSizeF svgViewbox = QgsApplication::svgCache()->svgViewboxSize( mPath, size, mColor, mStrokeColor, mStrokeWidth, widthScaleFactor );
2285 // set default aspect ratio
2286 mDefaultAspectRatio = svgViewbox.isValid() ? svgViewbox.height() / svgViewbox.width() : 0.0;
2287 }
2288 return mDefaultAspectRatio;
2289}
2290
2292{
2293 const bool aPreservedAspectRatio = preservedAspectRatio();
2294 if ( aPreservedAspectRatio && !par )
2295 {
2297 }
2298 else if ( !aPreservedAspectRatio && par )
2299 {
2300 mFixedAspectRatio = 0.0;
2301 }
2302 return preservedAspectRatio();
2303}
2304
2305void QgsSvgMarkerSymbolLayer::setParameters( const QMap<QString, QgsProperty> &parameters )
2306{
2308}
2309
2310
2312{
2313 return QStringLiteral( "SvgMarker" );
2314}
2315
2317{
2318 QgsMarkerSymbolLayer::startRender( context ); // get anchor point expressions
2319 Q_UNUSED( context )
2320}
2321
2323{
2324 Q_UNUSED( context )
2325}
2326
2328{
2329 QPainter *p = context.renderContext().painter();
2330 if ( !p )
2331 return;
2332
2333 bool hasDataDefinedSize = false;
2334 const double scaledWidth = calculateSize( context, hasDataDefinedSize );
2335 const double devicePixelRatio = context.renderContext().devicePixelRatio();
2336 const double width = context.renderContext().convertToPainterUnits( scaledWidth, mSizeUnit, mSizeMapUnitScale );
2337
2338 //don't render symbols with a width below one or above 10,000 pixels
2339 if ( static_cast< int >( width ) < 1 || 10000.0 < width )
2340 {
2341 return;
2342 }
2343
2344 const QgsScopedQPainterState painterState( p );
2345
2346 bool hasDataDefinedAspectRatio = false;
2347 const double aspectRatio = calculateAspectRatio( context, scaledWidth, hasDataDefinedAspectRatio );
2348 double scaledHeight = scaledWidth * ( !qgsDoubleNear( aspectRatio, 0.0 ) ? aspectRatio : mDefaultAspectRatio );
2349
2351
2352 double strokeWidth = mStrokeWidth;
2354 {
2357 }
2359
2360 QColor fillColor = mColor;
2361 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
2362 if ( useSelectedColor && mHasFillParam )
2363 {
2365 }
2367 {
2370 }
2371
2372 QColor strokeColor = mStrokeColor;
2374 {
2377 }
2378
2379 QString path = mPath;
2381 {
2384 context.renderContext().pathResolver() );
2386 {
2387 // adjust height of data defined path
2388 const QSizeF svgViewbox = QgsApplication::svgCache()->svgViewboxSize( path, scaledWidth, fillColor, strokeColor, strokeWidth,
2389 context.renderContext().scaleFactor(), aspectRatio,
2390 ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), evaluatedParameters );
2391 scaledHeight = svgViewbox.isValid() ? scaledWidth * svgViewbox.height() / svgViewbox.width() : scaledWidth;
2392 }
2393 }
2394
2395 QPointF outputOffset;
2396 double angle = 0.0;
2397 calculateOffsetAndRotation( context, scaledWidth, scaledHeight, outputOffset, angle );
2398
2399 p->translate( point + outputOffset );
2400
2401 const bool rotated = !qgsDoubleNear( angle, 0 );
2402 if ( rotated )
2403 p->rotate( angle );
2404
2405 bool fitsInCache = true;
2406 bool usePict = true;
2408 if ( ( !context.renderContext().forceVectorOutput() && !rotated ) || ( useSelectedColor && rasterizeSelected ) )
2409 {
2410 QImage img = QgsApplication::svgCache()->svgAsImage( path, width * devicePixelRatio, fillColor, strokeColor, strokeWidth,
2411 context.renderContext().scaleFactor(), fitsInCache, aspectRatio,
2412 ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), evaluatedParameters );
2413 if ( fitsInCache && img.width() > 1 )
2414 {
2415 usePict = false;
2416
2417 if ( useSelectedColor )
2418 {
2420 }
2421
2422 //consider transparency
2423 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
2424 {
2425 QImage transparentImage = img.copy();
2426 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
2427 if ( devicePixelRatio == 1 )
2428 {
2429 p->drawImage( -transparentImage.width() / 2.0, -transparentImage.height() / 2.0, transparentImage );
2430 }
2431 else
2432 {
2433 p->drawImage( QRectF( -transparentImage.width() / 2.0 / devicePixelRatio, -transparentImage.height() / 2.0 / devicePixelRatio,
2434 transparentImage.width() / devicePixelRatio, transparentImage.height() / devicePixelRatio
2435 ), transparentImage );
2436 }
2437 }
2438 else
2439 {
2440 if ( devicePixelRatio == 1 )
2441 {
2442 p->drawImage( -img.width() / 2.0, -img.height() / 2.0, img );
2443 }
2444 else
2445 {
2446 p->drawImage( QRectF( -img.width() / 2.0 / devicePixelRatio, -img.height() / 2.0 / devicePixelRatio,
2447 img.width() / devicePixelRatio, img.height() / devicePixelRatio ), img );
2448 }
2449 }
2450 }
2451 }
2452
2453 if ( usePict || !fitsInCache )
2454 {
2455 p->setOpacity( context.opacity() );
2457 context.renderContext().scaleFactor(), context.renderContext().forceVectorOutput(), aspectRatio,
2458 ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), evaluatedParameters );
2459 if ( pct.width() > 1 )
2460 {
2461 const QgsScopedQPainterState painterPictureState( p );
2462 _fixQPictureDPI( p );
2463 p->drawPicture( 0, 0, pct );
2464 }
2465 }
2466
2467 // workaround issue with nested QPictures forgetting antialiasing flag - see https://github.com/qgis/QGIS/issues/22909
2469}
2470
2471double QgsSvgMarkerSymbolLayer::calculateSize( QgsSymbolRenderContext &context, bool &hasDataDefinedSize ) const
2472{
2473 double scaledSize = mSize;
2475
2476 bool ok = true;
2477 if ( hasDataDefinedSize )
2478 {
2481 }
2482 else
2483 {
2485 if ( hasDataDefinedSize )
2486 {
2489 }
2490 }
2491
2492 if ( hasDataDefinedSize && ok )
2493 {
2494 switch ( mScaleMethod )
2495 {
2497 scaledSize = std::sqrt( scaledSize );
2498 break;
2500 break;
2501 }
2502 }
2503
2504 return scaledSize;
2505}
2506
2507double QgsSvgMarkerSymbolLayer::calculateAspectRatio( QgsSymbolRenderContext &context, double scaledSize, bool &hasDataDefinedAspectRatio ) const
2508{
2510 if ( !hasDataDefinedAspectRatio )
2511 return mFixedAspectRatio;
2512
2514 return 0.0;
2515
2516 double scaledAspectRatio = mDefaultAspectRatio;
2517 if ( mFixedAspectRatio > 0.0 )
2518 scaledAspectRatio = mFixedAspectRatio;
2519
2520 const double defaultHeight = mSize * scaledAspectRatio;
2521 scaledAspectRatio = defaultHeight / scaledSize;
2522
2523 bool ok = true;
2524 double scaledHeight = scaledSize * scaledAspectRatio;
2526 {
2527 context.setOriginalValueVariable( defaultHeight );
2529 }
2530
2531 if ( hasDataDefinedAspectRatio && ok )
2532 {
2533 switch ( mScaleMethod )
2534 {
2536 scaledHeight = sqrt( scaledHeight );
2537 break;
2539 break;
2540 }
2541 }
2542
2543 scaledAspectRatio = scaledHeight / scaledSize;
2544
2545 return scaledAspectRatio;
2546}
2547
2548void QgsSvgMarkerSymbolLayer::calculateOffsetAndRotation( QgsSymbolRenderContext &context, double scaledWidth, double scaledHeight, QPointF &offset, double &angle ) const
2549{
2550 //offset
2551 double offsetX = 0;
2552 double offsetY = 0;
2553 markerOffset( context, scaledWidth, scaledHeight, offsetX, offsetY );
2554 offset = QPointF( offsetX, offsetY );
2555
2558 {
2561 }
2562
2564 if ( hasDataDefinedRotation )
2565 {
2566 // For non-point markers, "dataDefinedRotation" means following the
2567 // shape (shape-data defined). For them, "field-data defined" does
2568 // not work at all. TODO: if "field-data defined" ever gets implemented
2569 // we'll need a way to distinguish here between the two, possibly
2570 // using another flag in renderHints()
2571 const QgsFeature *f = context.feature();
2572 if ( f )
2573 {
2574 if ( f->hasGeometry() && f->geometry().type() == Qgis::GeometryType::Point )
2575 {
2576 const QgsMapToPixel &m2p = context.renderContext().mapToPixel();
2577 angle += m2p.mapRotation();
2578 }
2579 }
2580 }
2581
2582 if ( angle )
2584}
2585
2586
2588{
2589 QVariantMap map;
2590 map[QStringLiteral( "name" )] = mPath;
2591 map[QStringLiteral( "size" )] = QString::number( mSize );
2592 map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( mSizeUnit );
2593 map[QStringLiteral( "size_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
2594 map[QStringLiteral( "fixedAspectRatio" )] = QString::number( mFixedAspectRatio );
2595 map[QStringLiteral( "angle" )] = QString::number( mAngle );
2596 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
2597 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
2598 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
2599 map[QStringLiteral( "scale_method" )] = QgsSymbolLayerUtils::encodeScaleMethod( mScaleMethod );
2600 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mColor );
2601 map[QStringLiteral( "outline_color" )] = QgsColorUtils::colorToString( mStrokeColor );
2602 map[QStringLiteral( "outline_width" )] = QString::number( mStrokeWidth );
2603 map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( mStrokeWidthUnit );
2604 map[QStringLiteral( "outline_width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale );
2605 map[QStringLiteral( "horizontal_anchor_point" )] = QString::number( mHorizontalAnchorPoint );
2606 map[QStringLiteral( "vertical_anchor_point" )] = QString::number( mVerticalAnchorPoint );
2607
2608 map[QStringLiteral( "parameters" )] = QgsProperty::propertyMapToVariantMap( mParameters );
2609
2610 return map;
2611}
2612
2614{
2618}
2619
2621{
2624 m->setColor( mColor );
2629 m->setOffset( mOffset );
2632 m->setSizeUnit( mSizeUnit );
2637
2639 copyPaintEffect( m );
2640 return m;
2641}
2642
2644{
2646 mStrokeWidthUnit = unit;
2647}
2648
2650{
2652 if ( unit != mStrokeWidthUnit )
2653 {
2655 }
2656 return unit;
2657}
2658
2660{
2663}
2664
2666{
2668 {
2670 }
2671 return QgsMapUnitScale();
2672}
2673
2674void QgsSvgMarkerSymbolLayer::writeSldMarker( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2675{
2676 // <Graphic>
2677 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
2678 element.appendChild( graphicElem );
2679
2680 // encode a parametric SVG reference
2681 const double size = QgsSymbolLayerUtils::rescaleUom( mSize, mSizeUnit, props );
2684
2685 // <Rotation>
2686 QString angleFunc;
2687 bool ok;
2688 const double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
2689 if ( !ok )
2690 {
2691 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
2692 }
2693 else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
2694 {
2695 angleFunc = QString::number( angle + mAngle );
2696 }
2697
2698 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
2699
2700 // <Displacement>
2701 const QPointF offset = QgsSymbolLayerUtils::rescaleUom( mOffset, mOffsetUnit, props );
2703}
2704
2706{
2707 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2708
2709 QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
2710 if ( graphicElem.isNull() )
2711 return nullptr;
2712
2713 QString path, mimeType;
2714 // Unused and to be DEPRECATED in externalGraphicFromSld
2715 QColor fillColor_;
2716 double size;
2717
2718 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor_, size ) )
2719 return nullptr;
2720
2721 double scaleFactor = 1.0;
2722 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2723 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2724 size = size * scaleFactor;
2725
2726 if ( mimeType != QLatin1String( "image/svg+xml" ) )
2727 return nullptr;
2728
2729 double angle = 0.0;
2730 QString angleFunc;
2731 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
2732 {
2733 bool ok;
2734 const double d = angleFunc.toDouble( &ok );
2735 if ( ok )
2736 angle = d;
2737 }
2738
2739 QPointF offset;
2741
2742 // Extract parameters from URL
2743 QString realPath { path };
2744 QUrl svgUrl { path };
2745
2746 // Because color definition can start with '#', the url parsing won't recognize the query string entirely
2747 QUrlQuery queryString;
2748
2749 if ( svgUrl.hasQuery() && svgUrl.hasFragment() )
2750 {
2751 const QString queryPart { path.mid( path.indexOf( '?' ) + 1 ) };
2752 queryString.setQuery( queryPart );
2753 }
2754
2755 // Remove query for simple file paths
2756 if ( svgUrl.scheme().isEmpty() || svgUrl.isLocalFile() )
2757 {
2758 svgUrl.setQuery( QString() );
2759 realPath = svgUrl.path();
2760 }
2761
2763
2764 QMap<QString, QgsProperty> params;
2765
2766 bool ok;
2767
2768 if ( queryString.hasQueryItem( QStringLiteral( "fill" ) ) )
2769 {
2770 const QColor fillColor { queryString.queryItemValue( QStringLiteral( "fill" ) ) };
2771 m->setFillColor( fillColor );
2772 }
2773
2774 if ( queryString.hasQueryItem( QStringLiteral( "fill-opacity" ) ) )
2775 {
2776 const double alpha { queryString.queryItemValue( QStringLiteral( "fill-opacity" ) ).toDouble( &ok ) };
2777 if ( ok )
2778 {
2779 params.insert( QStringLiteral( "fill-opacity" ), QgsProperty::fromValue( alpha ) );
2780 }
2781 }
2782
2783 if ( queryString.hasQueryItem( QStringLiteral( "outline" ) ) )
2784 {
2785 const QColor strokeColor { queryString.queryItemValue( QStringLiteral( "outline" ) ) };
2787 }
2788
2789 if ( queryString.hasQueryItem( QStringLiteral( "outline-opacity" ) ) )
2790 {
2791 const double alpha { queryString.queryItemValue( QStringLiteral( "outline-opacity" ) ).toDouble( &ok ) };
2792 if ( ok )
2793 {
2794 params.insert( QStringLiteral( "outline-opacity" ), QgsProperty::fromValue( alpha ) );
2795 }
2796 }
2797
2798 if ( queryString.hasQueryItem( QStringLiteral( "outline-width" ) ) )
2799 {
2800 const int width { queryString.queryItemValue( QStringLiteral( "outline-width" ) ).toInt( &ok )};
2801 if ( ok )
2802 {
2803 m->setStrokeWidth( width );
2804 }
2805 }
2806
2807 if ( ! params.isEmpty() )
2808 {
2809 m->setParameters( params );
2810 }
2811
2812 m->setOutputUnit( sldUnitSize );
2813 m->setAngle( angle );
2814 m->setOffset( offset );
2815 return m;
2816}
2817
2818bool QgsSvgMarkerSymbolLayer::writeDxf( QgsDxfExport &e, double mmMapUnitScaleFactor, const QString &layerName, QgsSymbolRenderContext &context, QPointF shift ) const
2819{
2820 //size
2821 double size = mSize;
2822
2823 const bool hasDataDefinedSize = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Size );
2824
2825 bool ok = true;
2826 if ( hasDataDefinedSize )
2827 {
2830 }
2831
2832 if ( hasDataDefinedSize && ok )
2833 {
2834 switch ( mScaleMethod )
2835 {
2837 size = std::sqrt( size );
2838 break;
2840 break;
2841 }
2842 }
2843
2845 {
2846 size *= mmMapUnitScaleFactor;
2847 }
2848
2849//offset, angle
2850 QPointF offset = mOffset;
2851
2853 {
2856 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
2857 if ( ok )
2858 offset = res;
2859 }
2860 const double offsetX = offset.x();
2861 const double offsetY = offset.y();
2862
2863 QPointF outputOffset( offsetX, offsetY );
2864
2865 double angle = mAngle + mLineAngle;
2867 {
2870 }
2871
2872 if ( angle )
2873 outputOffset = _rotatedOffset( outputOffset, angle );
2874
2876
2877 QString path = mPath;
2879 {
2882 context.renderContext().pathResolver() );
2883 }
2884
2885 double strokeWidth = mStrokeWidth;
2887 {
2890 }
2892
2893 QColor fillColor = mColor;
2895 {
2898 }
2899
2900 QColor strokeColor = mStrokeColor;
2902 {
2905 }
2906
2908
2909 const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, size, fillColor, strokeColor, strokeWidth,
2911 ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), evaluatedParameters );
2912
2913 QSvgRenderer r( svgContent );
2914 if ( !r.isValid() )
2915 return false;
2916
2917 QgsDxfPaintDevice pd( &e );
2918 pd.setDrawingSize( QSizeF( r.defaultSize() ) );
2919
2920 QSizeF outSize( r.defaultSize() );
2921 outSize.scale( size, size, Qt::KeepAspectRatio );
2922
2923 QPainter p;
2924 p.begin( &pd );
2925 if ( !qgsDoubleNear( angle, 0.0 ) )
2926 {
2927 p.translate( r.defaultSize().width() / 2.0, r.defaultSize().height() / 2.0 );
2928 p.rotate( angle );
2929 p.translate( -r.defaultSize().width() / 2.0, -r.defaultSize().height() / 2.0 );
2930 }
2931 pd.setShift( shift + QPointF( outputOffset.x(), -outputOffset.y() ) );
2932 pd.setOutputSize( QRectF( -outSize.width() / 2.0, -outSize.height() / 2.0, outSize.width(), outSize.height() ) );
2933 pd.setLayer( layerName );
2934 r.render( &p );
2935 p.end();
2936 return true;
2937}
2938
2940{
2941 bool hasDataDefinedSize = false;
2942 double scaledWidth = calculateSize( context, hasDataDefinedSize );
2943
2944 bool hasDataDefinedAspectRatio = false;
2945 const double aspectRatio = calculateAspectRatio( context, scaledWidth, hasDataDefinedAspectRatio );
2946 double scaledHeight = scaledWidth * ( !qgsDoubleNear( aspectRatio, 0.0 ) ? aspectRatio : mDefaultAspectRatio );
2947
2948 scaledWidth = context.renderContext().convertToPainterUnits( scaledWidth, mSizeUnit, mSizeMapUnitScale );
2949 scaledHeight = context.renderContext().convertToPainterUnits( scaledHeight, mSizeUnit, mSizeMapUnitScale );
2950
2951 //don't render symbols with size below one or above 10,000 pixels
2952 if ( static_cast< int >( scaledWidth ) < 1 || 10000.0 < scaledWidth )
2953 {
2954 return QRectF();
2955 }
2956
2957 QPointF outputOffset;
2958 double angle = 0.0;
2959 calculateOffsetAndRotation( context, scaledWidth, scaledHeight, outputOffset, angle );
2960
2961 double strokeWidth = mStrokeWidth;
2963 {
2966 }
2968
2969 QString path = mPath;
2971 {
2974 context.renderContext().pathResolver() );
2976 {
2977 // need to get colors to take advantage of cached SVGs
2978 QColor fillColor = mColor;
2980 {
2983 }
2984
2985 const QColor strokeColor = mStrokeColor;
2987 {
2990 }
2991
2993
2994 // adjust height of data defined path
2995 const QSizeF svgViewbox = QgsApplication::svgCache()->svgViewboxSize( path, scaledWidth, fillColor, strokeColor, strokeWidth,
2996 context.renderContext().scaleFactor(), aspectRatio,
2997 ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), evaluatedParameters );
2998 scaledHeight = svgViewbox.isValid() ? scaledWidth * svgViewbox.height() / svgViewbox.width() : scaledWidth;
2999 }
3000 }
3001
3002 QTransform transform;
3003 // move to the desired position
3004 transform.translate( point.x() + outputOffset.x(), point.y() + outputOffset.y() );
3005
3006 if ( !qgsDoubleNear( angle, 0.0 ) )
3007 transform.rotate( angle );
3008
3009 //antialiasing
3010 strokeWidth += 1.0 / 2.0;
3011
3012 QRectF symbolBounds = transform.mapRect( QRectF( -scaledWidth / 2.0,
3013 -scaledHeight / 2.0,
3014 scaledWidth,
3015 scaledHeight ) );
3016
3017 //extend bounds by pen width / 2.0
3018 symbolBounds.adjust( -strokeWidth / 2.0, -strokeWidth / 2.0,
3019 strokeWidth / 2.0, strokeWidth / 2.0 );
3020
3021 return symbolBounds;
3022}
3023
3025
3026QgsRasterMarkerSymbolLayer::QgsRasterMarkerSymbolLayer( const QString &path, double size, double angle, Qgis::ScaleMethod scaleMethod )
3027 : mPath( path )
3028{
3029 mSize = size;
3030 mAngle = angle;
3031 mOffset = QPointF( 0, 0 );
3034}
3035
3037
3039{
3040 QString path;
3044
3045 if ( props.contains( QStringLiteral( "imageFile" ) ) )
3046 path = props[QStringLiteral( "imageFile" )].toString();
3047 if ( props.contains( QStringLiteral( "size" ) ) )
3048 size = props[QStringLiteral( "size" )].toDouble();
3049 if ( props.contains( QStringLiteral( "angle" ) ) )
3050 angle = props[QStringLiteral( "angle" )].toDouble();
3051 if ( props.contains( QStringLiteral( "scale_method" ) ) )
3052 scaleMethod = QgsSymbolLayerUtils::decodeScaleMethod( props[QStringLiteral( "scale_method" )].toString() );
3053
3054 std::unique_ptr< QgsRasterMarkerSymbolLayer > m = std::make_unique< QgsRasterMarkerSymbolLayer >( path, size, angle, scaleMethod );
3055 m->setCommonProperties( props );
3056 return m.release();
3057}
3058
3059void QgsRasterMarkerSymbolLayer::setCommonProperties( const QVariantMap &properties )
3060{
3061 if ( properties.contains( QStringLiteral( "alpha" ) ) )
3062 {
3063 setOpacity( properties[QStringLiteral( "alpha" )].toDouble() );
3064 }
3065
3066 if ( properties.contains( QStringLiteral( "size_unit" ) ) )
3067 setSizeUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "size_unit" )].toString() ) );
3068 if ( properties.contains( QStringLiteral( "size_map_unit_scale" ) ) )
3069 setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "size_map_unit_scale" )].toString() ) );
3070 if ( properties.contains( QStringLiteral( "fixedAspectRatio" ) ) )
3071 setFixedAspectRatio( properties[QStringLiteral( "fixedAspectRatio" )].toDouble() );
3072
3073 if ( properties.contains( QStringLiteral( "offset" ) ) )
3074 setOffset( QgsSymbolLayerUtils::decodePoint( properties[QStringLiteral( "offset" )].toString() ) );
3075 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
3076 setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
3077 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3078 setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3079
3080 if ( properties.contains( QStringLiteral( "horizontal_anchor_point" ) ) )
3081 {
3082 setHorizontalAnchorPoint( QgsMarkerSymbolLayer::HorizontalAnchorPoint( properties[ QStringLiteral( "horizontal_anchor_point" )].toInt() ) );
3083 }
3084 if ( properties.contains( QStringLiteral( "vertical_anchor_point" ) ) )
3085 {
3086 setVerticalAnchorPoint( QgsMarkerSymbolLayer::VerticalAnchorPoint( properties[ QStringLiteral( "vertical_anchor_point" )].toInt() ) );
3087 }
3088
3091}
3092
3093void QgsRasterMarkerSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
3094{
3095 const QVariantMap::iterator it = properties.find( QStringLiteral( "name" ) );
3096 if ( it != properties.end() && it.value().type() == QVariant::String )
3097 {
3098 if ( saving )
3099 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
3100 else
3101 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
3102 }
3103}
3104
3105void QgsRasterMarkerSymbolLayer::setPath( const QString &path )
3106{
3107 mPath = path;
3109}
3110
3112{
3113 const bool aPreservedAspectRatio = preservedAspectRatio();
3114 if ( aPreservedAspectRatio && !par )
3115 {
3117 }
3118 else if ( !aPreservedAspectRatio && par )
3119 {
3120 mFixedAspectRatio = 0.0;
3121 }
3122 return preservedAspectRatio();
3123}
3124
3126{
3127 if ( mDefaultAspectRatio == 0.0 )
3128 {
3130 mDefaultAspectRatio = ( !size.isNull() && size.isValid() && size.width() > 0 ) ? static_cast< double >( size.height() ) / static_cast< double >( size.width() ) : 0.0;
3131 }
3132 return mDefaultAspectRatio;
3133}
3134
3136{
3137 return QStringLiteral( "RasterMarker" );
3138}
3139
3141{
3142 QPainter *p = context.renderContext().painter();
3143 if ( !p )
3144 return;
3145
3146 QString path = mPath;
3148 {
3151 }
3152
3153 if ( path.isEmpty() )
3154 return;
3155
3156 double width = 0.0;
3157 double height = 0.0;
3158
3159 bool hasDataDefinedSize = false;
3160 const double scaledSize = calculateSize( context, hasDataDefinedSize );
3161
3162 bool hasDataDefinedAspectRatio = false;
3163 const double aspectRatio = calculateAspectRatio( context, scaledSize, hasDataDefinedAspectRatio );
3164
3165 QPointF outputOffset;
3166 double angle = 0.0;
3167
3168 // RenderPercentage Unit Type takes original image size
3170 {
3172 if ( size.isEmpty() )
3173 return;
3174
3175 width = ( scaledSize * static_cast< double >( size.width() ) ) / 100.0;
3176 height = ( scaledSize * static_cast< double >( size.height() ) ) / 100.0;
3177
3178 // don't render symbols with size below one or above 10,000 pixels
3179 if ( static_cast< int >( width ) < 1 || 10000.0 < width || static_cast< int >( height ) < 1 || 10000.0 < height )
3180 return;
3181
3182 calculateOffsetAndRotation( context, width, height, outputOffset, angle );
3183 }
3184 else
3185 {
3186 width = context.renderContext().convertToPainterUnits( scaledSize, mSizeUnit, mSizeMapUnitScale );
3187 height = width * ( preservedAspectRatio() ? defaultAspectRatio() : aspectRatio );
3188
3189 if ( preservedAspectRatio() && path != mPath )
3190 {
3192 if ( !size.isNull() && size.isValid() && size.width() > 0 )
3193 {
3194 height = width * ( static_cast< double >( size.height() ) / static_cast< double >( size.width() ) );
3195 }
3196 }
3197
3198 // don't render symbols with size below one or above 10,000 pixels
3199 if ( static_cast< int >( width ) < 1 || 10000.0 < width )
3200 return;
3201
3202 calculateOffsetAndRotation( context, scaledSize, scaledSize * ( height / width ), outputOffset, angle );
3203 }
3204
3205 const QgsScopedQPainterState painterState( p );
3206 p->translate( point + outputOffset );
3207
3208 const bool rotated = !qgsDoubleNear( angle, 0 );
3209 if ( rotated )
3210 p->rotate( angle );
3211
3212 double opacity = mOpacity;
3214 {
3217 }
3218 opacity *= context.opacity();
3219
3220 QImage img = fetchImage( context.renderContext(), path, QSize( width, preservedAspectRatio() ? 0 : width * aspectRatio ), preservedAspectRatio(), opacity );
3221 if ( !img.isNull() )
3222 {
3223 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3224 if ( useSelectedColor )
3225 {
3227 }
3228
3229 p->drawImage( -img.width() / 2.0, -img.height() / 2.0, img );
3230 }
3231}
3232
3233QImage QgsRasterMarkerSymbolLayer::fetchImage( QgsRenderContext &context, const QString &path, QSize size, bool preserveAspectRatio, double opacity ) const
3234{
3235 bool cached = false;
3236 return QgsApplication::imageCache()->pathAsImage( path, size, preserveAspectRatio, opacity, cached, context.flags() & Qgis::RenderContextFlag::RenderBlocking );
3237}
3238
3239double QgsRasterMarkerSymbolLayer::calculateSize( QgsSymbolRenderContext &context, bool &hasDataDefinedSize ) const
3240{
3241 double scaledSize = mSize;
3243
3244 bool ok = true;
3245 if ( hasDataDefinedSize )
3246 {
3249 }
3250 else
3251 {
3253 if ( hasDataDefinedSize )
3254 {
3257 }
3258 }
3259
3260 if ( hasDataDefinedSize && ok )
3261 {
3262 switch ( mScaleMethod )
3263 {
3265 scaledSize = std::sqrt( scaledSize );
3266 break;
3268 break;
3269 }
3270 }
3271
3272 return scaledSize;
3273}
3274
3275double QgsRasterMarkerSymbolLayer::calculateAspectRatio( QgsSymbolRenderContext &context, double scaledSize, bool &hasDataDefinedAspectRatio ) const
3276{
3278 if ( !hasDataDefinedAspectRatio )
3279 return mFixedAspectRatio;
3280
3282 return 0.0;
3283
3284 double scaledAspectRatio = mDefaultAspectRatio;
3285 if ( mFixedAspectRatio > 0.0 )
3286 scaledAspectRatio = mFixedAspectRatio;
3287
3288 const double defaultHeight = mSize * scaledAspectRatio;
3289 scaledAspectRatio = defaultHeight / scaledSize;
3290
3291 bool ok = true;
3292 double scaledHeight = scaledSize * scaledAspectRatio;
3294 {
3295 context.setOriginalValueVariable( defaultHeight );
3297 }
3298
3299 if ( hasDataDefinedAspectRatio && ok )
3300 {
3301 switch ( mScaleMethod )
3302 {
3304 scaledHeight = sqrt( scaledHeight );
3305 break;
3307 break;
3308 }
3309 }
3310
3311 scaledAspectRatio = scaledHeight / scaledSize;
3312
3313 return scaledAspectRatio;
3314}
3315
3316void QgsRasterMarkerSymbolLayer::calculateOffsetAndRotation( QgsSymbolRenderContext &context, double scaledWidth, double scaledHeight, QPointF &offset, double &angle ) const
3317{
3318 //offset
3319 double offsetX = 0;
3320 double offsetY = 0;
3321 markerOffset( context, scaledWidth, scaledHeight, offsetX, offsetY );
3322 offset = QPointF( offsetX, offsetY );
3323
3326 {
3329 }
3330
3332 if ( hasDataDefinedRotation )
3333 {
3334 const QgsFeature *f = context.feature();
3335 if ( f )
3336 {
3337 if ( f->hasGeometry() && f->geometry().type() == Qgis::GeometryType::Point )
3338 {
3339 const QgsMapToPixel &m2p = context.renderContext().mapToPixel();
3340 angle += m2p.mapRotation();
3341 }
3342 }
3343 }
3344
3345 if ( angle )
3347}
3348
3349
3351{
3352 QVariantMap map;
3353 map[QStringLiteral( "imageFile" )] = mPath;
3354 map[QStringLiteral( "size" )] = QString::number( mSize );
3355 map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( mSizeUnit );
3356 map[QStringLiteral( "size_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
3357 map[QStringLiteral( "fixedAspectRatio" )] = QString::number( mFixedAspectRatio );
3358 map[QStringLiteral( "angle" )] = QString::number( mAngle );
3359 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
3360 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
3361 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3362 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3363 map[QStringLiteral( "scale_method" )] = QgsSymbolLayerUtils::encodeScaleMethod( mScaleMethod );
3364 map[QStringLiteral( "horizontal_anchor_point" )] = QString::number( mHorizontalAnchorPoint );
3365 map[QStringLiteral( "vertical_anchor_point" )] = QString::number( mVerticalAnchorPoint );
3366 return map;
3367}
3368
3370{
3371 std::unique_ptr< QgsRasterMarkerSymbolLayer > m = std::make_unique< QgsRasterMarkerSymbolLayer >( mPath, mSize, mAngle );
3372 copyCommonProperties( m.get() );
3373 return m.release();
3374}
3375
3376
3378{
3380 other->setOpacity( mOpacity );
3381 other->setOffset( mOffset );
3382 other->setOffsetUnit( mOffsetUnit );
3384 other->setSizeUnit( mSizeUnit );
3389 copyPaintEffect( other );
3390}
3391
3393{
3396}
3397
3399{
3400 return QColor();
3401}
3402
3404{
3406}
3407
3409{
3411}
3412
3414{
3415 bool hasDataDefinedSize = false;
3416 const double scaledSize = calculateSize( context, hasDataDefinedSize );
3417 const double width = context.renderContext().convertToPainterUnits( scaledSize, mSizeUnit, mSizeMapUnitScale );
3418 bool hasDataDefinedAspectRatio = false;
3419 const double aspectRatio = calculateAspectRatio( context, scaledSize, hasDataDefinedAspectRatio );
3420 const double height = width * ( preservedAspectRatio() ? defaultAspectRatio() : aspectRatio );
3421
3422 //don't render symbols with size below one or above 10,000 pixels
3423 if ( static_cast< int >( scaledSize ) < 1 || 10000.0 < scaledSize )
3424 {
3425 return QRectF();
3426 }
3427
3428 QPointF outputOffset;
3429 double angle = 0.0;
3430 calculateOffsetAndRotation( context, scaledSize, scaledSize * ( height / width ), outputOffset, angle );
3431
3432 QTransform transform;
3433
3434 // move to the desired position
3435 transform.translate( point.x() + outputOffset.x(), point.y() + outputOffset.y() );
3436
3437 if ( !qgsDoubleNear( angle, 0.0 ) )
3438 transform.rotate( angle );
3439
3440 QRectF symbolBounds = transform.mapRect( QRectF( -width / 2.0,
3441 -height / 2.0,
3442 width,
3443 height ) );
3444
3445 return symbolBounds;
3446}
3447
3449
3450QgsFontMarkerSymbolLayer::QgsFontMarkerSymbolLayer( const QString &fontFamily, QString chr, double pointSize, const QColor &color, double angle )
3451{
3452 mFontFamily = fontFamily;
3453 mString = chr;
3454 mColor = color;
3455 mAngle = angle;
3456 mSize = pointSize;
3457 mOrigSize = pointSize;
3459 mOffset = QPointF( 0, 0 );
3461 mStrokeColor = DEFAULT_FONTMARKER_BORDERCOLOR;
3462 mStrokeWidth = 0.0;
3463 mStrokeWidthUnit = Qgis::RenderUnit::Millimeters;
3464 mPenJoinStyle = DEFAULT_FONTMARKER_JOINSTYLE;
3465}
3466
3468
3470{
3472 QString string = DEFAULT_FONTMARKER_CHR;
3473 double pointSize = DEFAULT_FONTMARKER_SIZE;
3476
3477 if ( props.contains( QStringLiteral( "font" ) ) )
3478 fontFamily = props[QStringLiteral( "font" )].toString();
3479 if ( props.contains( QStringLiteral( "chr" ) ) && props[QStringLiteral( "chr" )].toString().length() > 0 )
3480 {
3481 string = props["chr"].toString();
3482 const thread_local QRegularExpression charRegExp( QStringLiteral( "%1([0-9]+)%1" ).arg( FONTMARKER_CHR_FIX ) );
3483 QRegularExpressionMatch match = charRegExp.match( string );
3484 while ( match.hasMatch() )
3485 {
3486 QChar replacement = QChar( match.captured( 1 ).toUShort() );
3487 string = string.mid( 0, match.capturedStart( 0 ) ) + replacement + string.mid( match.capturedEnd( 0 ) );
3488 match = charRegExp.match( string );
3489 }
3490 }
3491
3492 if ( props.contains( QStringLiteral( "size" ) ) )
3493 pointSize = props[QStringLiteral( "size" )].toDouble();
3494 if ( props.contains( QStringLiteral( "color" ) ) )
3495 color = QgsColorUtils::colorFromString( props[QStringLiteral( "color" )].toString() );
3496 if ( props.contains( QStringLiteral( "angle" ) ) )
3497 angle = props[QStringLiteral( "angle" )].toDouble();
3498
3500
3501 if ( props.contains( QStringLiteral( "font_style" ) ) )
3502 m->setFontStyle( props[QStringLiteral( "font_style" )].toString() );
3503 if ( props.contains( QStringLiteral( "outline_color" ) ) )
3504 m->setStrokeColor( QgsColorUtils::colorFromString( props[QStringLiteral( "outline_color" )].toString() ) );
3505 if ( props.contains( QStringLiteral( "outline_width" ) ) )
3506 m->setStrokeWidth( props[QStringLiteral( "outline_width" )].toDouble() );
3507 if ( props.contains( QStringLiteral( "offset" ) ) )
3508 m->setOffset( QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() ) );
3509 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
3510 m->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
3511 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
3512 m->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
3513 if ( props.contains( QStringLiteral( "size_unit" ) ) )
3514 m->setSizeUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "size_unit" )].toString() ) );
3515 if ( props.contains( QStringLiteral( "size_map_unit_scale" ) ) )
3516 m->setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "size_map_unit_scale" )].toString() ) );
3517 if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
3518 m->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
3519 if ( props.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
3520 m->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
3521 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
3522 m->setPenJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() ) );
3523 if ( props.contains( QStringLiteral( "horizontal_anchor_point" ) ) )
3524 m->setHorizontalAnchorPoint( QgsMarkerSymbolLayer::HorizontalAnchorPoint( props[ QStringLiteral( "horizontal_anchor_point" )].toInt() ) );
3525 if ( props.contains( QStringLiteral( "vertical_anchor_point" ) ) )
3526 m->setVerticalAnchorPoint( QgsMarkerSymbolLayer::VerticalAnchorPoint( props[ QStringLiteral( "vertical_anchor_point" )].toInt() ) );
3527
3529
3530 return m;
3531}
3532
3534{
3535 return QStringLiteral( "FontMarker" );
3536}
3537
3539{
3540 QColor brushColor = mColor;
3541 QColor penColor = mStrokeColor;
3542
3543 brushColor.setAlphaF( mColor.alphaF() * context.opacity() );
3544 penColor.setAlphaF( mStrokeColor.alphaF() * context.opacity() );
3545
3546 mBrush = QBrush( brushColor );
3547 mPen = QPen( penColor );
3548 mPen.setJoinStyle( mPenJoinStyle );
3549 mPen.setWidthF( context.renderContext().convertToPainterUnits( mStrokeWidth, mStrokeWidthUnit, mStrokeWidthMapUnitScale ) );
3550
3551 mFont = QgsFontUtils::createFont( QgsApplication::fontManager()->processFontFamilyName( mFontFamily ) );
3552 if ( !mFontStyle.isEmpty() )
3553 {
3554 mFont.setStyleName( QgsFontUtils::translateNamedStyle( mFontStyle ) );
3555 }
3556
3557 double sizePixels = context.renderContext().convertToPainterUnits( mSize, mSizeUnit, mSizeMapUnitScale );
3558 mNonZeroFontSize = !qgsDoubleNear( sizePixels, 0.0 );
3559
3560 if ( mNonZeroFontSize && sizePixels > MAX_FONT_CHARACTER_SIZE_IN_PIXELS )
3561 {
3562 // if font is too large (e.g using map units and map is very zoomed in), then we limit
3563 // the font size and instead scale up the painter.
3564 // this avoids issues with massive font sizes (eg https://github.com/qgis/QGIS/issues/42270)
3565 mFontSizeScale = sizePixels / MAX_FONT_CHARACTER_SIZE_IN_PIXELS;
3566 sizePixels = MAX_FONT_CHARACTER_SIZE_IN_PIXELS;
3567 }
3568 else
3569 mFontSizeScale = 1.0;
3570
3571 // if a non zero, but small pixel size results, round up to 2 pixels so that a "dot" is at least visible
3572 // (if we set a <=1 pixel size here Qt will reset the font to a default size, leading to much too large symbols)
3573 mFont.setPixelSize( std::max( 2, static_cast< int >( std::round( sizePixels ) ) ) );
3574 mFontMetrics.reset( new QFontMetrics( mFont ) );
3575 mChrWidth = mFontMetrics->horizontalAdvance( mString );
3576 mChrOffset = QPointF( mChrWidth / 2.0, -mFontMetrics->ascent() / 2.0 );
3577 mOrigSize = mSize; // save in case the size would be data defined
3578
3579 // use caching only when not using a data defined character
3583 if ( mUseCachedPath )
3584 {
3585 QPointF chrOffset = mChrOffset;
3586 double chrWidth;
3587 const QString charToRender = characterToRender( context, chrOffset, chrWidth );
3588 mCachedPath = QPainterPath();
3589 mCachedPath.addText( -chrOffset.x(), -chrOffset.y(), mFont, charToRender );
3590 }
3591}
3592
3594{
3595 Q_UNUSED( context )
3596}
3597
3598QString QgsFontMarkerSymbolLayer::characterToRender( QgsSymbolRenderContext &context, QPointF &charOffset, double &charWidth )
3599{
3600 charOffset = mChrOffset;
3601 QString stringToRender = mString;
3603 {
3604 context.setOriginalValueVariable( mString );
3606 if ( stringToRender != mString )
3607 {
3608 charWidth = mFontMetrics->horizontalAdvance( stringToRender );
3609 charOffset = QPointF( charWidth / 2.0, -mFontMetrics->ascent() / 2.0 );
3610 }
3611 }
3612 return stringToRender;
3613}
3614
3615void QgsFontMarkerSymbolLayer::calculateOffsetAndRotation( QgsSymbolRenderContext &context,
3616 double scaledSize,
3617 bool &hasDataDefinedRotation,
3618 QPointF &offset,
3619 double &angle ) const
3620{
3621 //offset
3622 double offsetX = 0;
3623 double offsetY = 0;
3624 markerOffset( context, scaledSize, scaledSize, offsetX, offsetY );
3625 offset = QPointF( offsetX, offsetY );
3626
3627 //angle
3628 bool ok = true;
3631 {
3634
3635 // If the expression evaluation was not successful, fallback to static value
3636 if ( !ok )
3638 }
3639
3640 hasDataDefinedRotation = context.renderHints() & Qgis::SymbolRenderHint::DynamicRotation;
3641 if ( hasDataDefinedRotation )
3642 {
3643 // For non-point markers, "dataDefinedRotation" means following the
3644 // shape (shape-data defined). For them, "field-data defined" does
3645 // not work at all. TODO: if "field-data defined" ever gets implemented
3646 // we'll need a way to distinguish here between the two, possibly
3647 // using another flag in renderHints()
3648 const QgsFeature *f = context.feature();
3649 if ( f )
3650 {
3651 if ( f->hasGeometry() && f->geometry().type() == Qgis::GeometryType::Point )
3652 {
3653 const QgsMapToPixel &m2p = context.renderContext().mapToPixel();
3654 angle += m2p.mapRotation();
3655 }
3656 }
3657 }
3658
3659 if ( angle )
3661}
3662
3663double QgsFontMarkerSymbolLayer::calculateSize( QgsSymbolRenderContext &context )
3664{
3665 double scaledSize = mSize;
3666 const bool hasDataDefinedSize = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Size );
3667
3668 bool ok = true;
3669 if ( hasDataDefinedSize )
3670 {
3673 }
3674
3675 if ( hasDataDefinedSize && ok )
3676 {
3677 switch ( mScaleMethod )
3678 {
3680 scaledSize = std::sqrt( scaledSize );
3681 break;
3683 break;
3684 }
3685 }
3686 return scaledSize;
3687}
3688
3690{
3691 QPainter *p = context.renderContext().painter();
3692 if ( !p || !mNonZeroFontSize )
3693 return;
3694
3695 QTransform transform;
3696
3697 bool ok;
3698 QColor brushColor = mColor;
3700 {
3703 }
3704 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3705 brushColor = useSelectedColor ? context.renderContext().selectionColor() : brushColor;
3706 if ( !useSelectedColor || !SELECTION_IS_OPAQUE )
3707 {
3708 brushColor.setAlphaF( brushColor.alphaF() * context.opacity() );
3709 }
3710 mBrush.setColor( brushColor );
3711
3712 QColor penColor = mStrokeColor;
3714 {
3717 }
3718 penColor.setAlphaF( penColor.alphaF() * context.opacity() );
3719
3720 double penWidth = context.renderContext().convertToPainterUnits( mStrokeWidth, mStrokeWidthUnit, mStrokeWidthMapUnitScale );
3722 {
3723 context.setOriginalValueVariable( mStrokeWidth );
3725 if ( ok )
3726 {
3727 penWidth = context.renderContext().convertToPainterUnits( strokeWidth, mStrokeWidthUnit, mStrokeWidthMapUnitScale );
3728 }
3729 }
3730
3732 {
3735 if ( ok )
3736 {
3737 mPen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
3738 }
3739 }
3740
3741 const QgsScopedQPainterState painterState( p );
3742 p->setBrush( mBrush );
3743 if ( !qgsDoubleNear( penWidth, 0.0 ) )
3744 {
3745 mPen.setColor( penColor );
3746 mPen.setWidthF( penWidth );
3747 p->setPen( mPen );
3748 }
3749 else
3750 {
3751 p->setPen( Qt::NoPen );
3752 }
3753
3755 {
3756 context.setOriginalValueVariable( mFontFamily );
3758 const QString processedFamily = QgsApplication::fontManager()->processFontFamilyName( ok ? fontFamily : mFontFamily );
3759 QgsFontUtils::setFontFamily( mFont, processedFamily );
3760 }
3762 {
3763 context.setOriginalValueVariable( mFontStyle );
3766 }
3768 {
3769 mFontMetrics.reset( new QFontMetrics( mFont ) );
3770 }
3771
3772 QPointF chrOffset = mChrOffset;
3773 double chrWidth;
3774 const QString charToRender = characterToRender( context, chrOffset, chrWidth );
3775
3776 const double sizeToRender = calculateSize( context );
3777
3778 bool hasDataDefinedRotation = false;
3779 QPointF offset;
3780 double angle = 0;
3781 calculateOffsetAndRotation( context, sizeToRender, hasDataDefinedRotation, offset, angle );
3782
3783 p->translate( point.x() + offset.x(), point.y() + offset.y() );
3784
3785 if ( !qgsDoubleNear( angle, 0.0 ) )
3786 transform.rotate( angle );
3787
3788 if ( !qgsDoubleNear( sizeToRender, mOrigSize ) )
3789 {
3790 const double s = sizeToRender / mOrigSize;
3791 transform.scale( s, s );
3792 }
3793
3794 if ( !qgsDoubleNear( mFontSizeScale, 1.0 ) )
3795 transform.scale( mFontSizeScale, mFontSizeScale );
3796
3797 if ( mUseCachedPath )
3798 {
3799 p->drawPath( transform.map( mCachedPath ) );
3800 }
3801 else
3802 {
3803 QPainterPath path;
3804 path.addText( -chrOffset.x(), -chrOffset.y(), mFont, charToRender );
3805 p->drawPath( transform.map( path ) );
3806 }
3807}
3808
3810{
3811 QVariantMap props;
3812 props[QStringLiteral( "font" )] = mFontFamily;
3813 props[QStringLiteral( "font_style" )] = mFontStyle;
3814 QString chr = mString;
3815 for ( int i = 0; i < 32; i++ )
3816 {
3817 if ( i == 9 || i == 10 || i == 13 )
3818 {
3819 continue;
3820 }
3821 chr.replace( QChar( i ), QStringLiteral( "%1%2%1" ).arg( FONTMARKER_CHR_FIX, QString::number( i ) ) );
3822 }
3823 props[QStringLiteral( "chr" )] = chr;
3824 props[QStringLiteral( "size" )] = QString::number( mSize );
3825 props[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( mSizeUnit );
3826 props[QStringLiteral( "size_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
3827 props[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mColor );
3828 props[QStringLiteral( "outline_color" )] = QgsColorUtils::colorToString( mStrokeColor );
3829 props[QStringLiteral( "outline_width" )] = QString::number( mStrokeWidth );
3830 props[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( mStrokeWidthUnit );
3831 props[QStringLiteral( "outline_width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale );
3832 props[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
3833 props[QStringLiteral( "angle" )] = QString::number( mAngle );
3834 props[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
3835 props[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
3836 props[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
3837 props[QStringLiteral( "horizontal_anchor_point" )] = QString::number( mHorizontalAnchorPoint );
3838 props[QStringLiteral( "vertical_anchor_point" )] = QString::number( mVerticalAnchorPoint );
3839 return props;
3840}
3841
3843{
3844 QgsFontMarkerSymbolLayer *m = new QgsFontMarkerSymbolLayer( mFontFamily, mString, mSize, mColor, mAngle );
3845 m->setFontStyle( mFontStyle );
3846 m->setStrokeColor( mStrokeColor );
3847 m->setStrokeWidth( mStrokeWidth );
3848 m->setStrokeWidthUnit( mStrokeWidthUnit );
3849 m->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
3850 m->setPenJoinStyle( mPenJoinStyle );
3851 m->setOffset( mOffset );
3854 m->setSizeUnit( mSizeUnit );
3859 copyPaintEffect( m );
3860 return m;
3861}
3862
3863void QgsFontMarkerSymbolLayer::writeSldMarker( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
3864{
3865 // <Graphic>
3866 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
3867 element.appendChild( graphicElem );
3868
3869 const QString fontPath = QStringLiteral( "ttf://%1" ).arg( mFontFamily );
3870 int markIndex = !mString.isEmpty() ? mString.at( 0 ).unicode() : 0;
3871 const double size = QgsSymbolLayerUtils::rescaleUom( mSize, mSizeUnit, props );
3872 QgsSymbolLayerUtils::externalMarkerToSld( doc, graphicElem, fontPath, QStringLiteral( "ttf" ), &markIndex, mColor, size );
3873
3874 // <Rotation>
3875 QString angleFunc;
3876 bool ok;
3877 const double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
3878 if ( !ok )
3879 {
3880 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
3881 }
3882 else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
3883 {
3884 angleFunc = QString::number( angle + mAngle );
3885 }
3886 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
3887
3888 // <Displacement>
3889 const QPointF offset = QgsSymbolLayerUtils::rescaleUom( mOffset, mOffsetUnit, props );
3891}
3892
3894{
3896 || mStrokeWidthUnit == Qgis::RenderUnit::MapUnits || mStrokeWidthUnit == Qgis::RenderUnit::MetersInMapUnits
3898}
3899
3901{
3903 mStrokeWidthUnit = unit;
3904}
3905
3907{
3908 QPointF chrOffset = mChrOffset;
3909 double chrWidth = mChrWidth;
3910 //calculate width of rendered character
3911 ( void )characterToRender( context, chrOffset, chrWidth );
3912
3913 if ( !mFontMetrics )
3914 mFontMetrics.reset( new QFontMetrics( mFont ) );
3915
3916 double scaledSize = calculateSize( context );
3917 if ( !qgsDoubleNear( scaledSize, mOrigSize ) )
3918 {
3919 chrWidth *= scaledSize / mOrigSize;
3920 }
3921 chrWidth *= mFontSizeScale;
3922
3923 bool hasDataDefinedRotation = false;
3924 QPointF offset;
3925 double angle = 0;
3926 calculateOffsetAndRotation( context, scaledSize, hasDataDefinedRotation, offset, angle );
3927 scaledSize = context.renderContext().convertToPainterUnits( scaledSize, mSizeUnit, mSizeMapUnitScale );
3928
3929 QTransform transform;
3930
3931 // move to the desired position
3932 transform.translate( point.x() + offset.x(), point.y() + offset.y() );
3933
3934 if ( !qgsDoubleNear( angle, 0.0 ) )
3935 transform.rotate( angle );
3936
3937 QRectF symbolBounds = transform.mapRect( QRectF( -chrWidth / 2.0,
3938 -scaledSize / 2.0,
3939 chrWidth,
3940 scaledSize ) );
3941 return symbolBounds;
3942}
3943
3945{
3946 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
3947
3948 QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
3949 if ( graphicElem.isNull() )
3950 return nullptr;
3951
3952 QString name, format;
3953 QColor color;
3954 double size;
3955 int chr;
3956
3957 if ( !QgsSymbolLayerUtils::externalMarkerFromSld( graphicElem, name, format, chr, color, size ) )
3958 return nullptr;
3959
3960 if ( !name.startsWith( QLatin1String( "ttf://" ) ) || format != QLatin1String( "ttf" ) )
3961 return nullptr;
3962
3963 const QString fontFamily = name.mid( 6 );
3964
3965 double angle = 0.0;
3966 QString angleFunc;
3967 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
3968 {
3969 bool ok;
3970 const double d = angleFunc.toDouble( &ok );
3971 if ( ok )
3972 angle = d;
3973 }
3974
3975 QPointF offset;
3977
3978 double scaleFactor = 1.0;
3979 const QString uom = element.attribute( QStringLiteral( "uom" ) );
3980 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
3981 offset.setX( offset.x() * scaleFactor );
3982 offset.setY( offset.y() * scaleFactor );
3983 size = size * scaleFactor;
3984
3986 m->setOutputUnit( sldUnitSize );
3987 m->setAngle( angle );
3988 m->setOffset( offset );
3989 return m;
3990}
3991
3992void QgsFontMarkerSymbolLayer::resolveFonts( const QVariantMap &properties, const QgsReadWriteContext &context )
3993{
3994 const QString fontFamily = properties.value( QStringLiteral( "font" ), DEFAULT_FONTMARKER_FONT ).toString();
3995 const QString processedFamily = QgsApplication::fontManager()->processFontFamilyName( fontFamily );
3996 QString matched;
3997 if ( !QgsFontUtils::fontFamilyMatchOnSystem( processedFamily )
3998 && !QgsApplication::fontManager()->tryToDownloadFontFamily( processedFamily, matched ) )
3999 {
4000 context.pushMessage( QObject::tr( "Font “%1” not available on system" ).arg( processedFamily ) );
4001 }
4002}
4003
4005{
4006 QMap<QString, QgsProperty>::iterator it = mParameters.begin();
4007 for ( ; it != mParameters.end(); ++it )
4008 it.value().prepare( context.renderContext().expressionContext() );
4009
4011}
4012
4013
4015{
4016 QSet<QString> attrs = QgsMarkerSymbolLayer::usedAttributes( context );
4017
4018 QMap<QString, QgsProperty>::const_iterator it = mParameters.constBegin();
4019 for ( ; it != mParameters.constEnd(); ++it )
4020 {
4021 attrs.unite( it.value().referencedFields( context.expressionContext(), true ) );
4022 }
4023
4024 return attrs;
4025}
4026
4027//
4028// QgsAnimatedMarkerSymbolLayer
4029//
4030
4032 : QgsRasterMarkerSymbolLayer( path, size, angle )
4033{
4034
4035}
4036
4038
4040{
4041 QString path;
4044
4045 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
4046 path = properties[QStringLiteral( "imageFile" )].toString();
4047 if ( properties.contains( QStringLiteral( "size" ) ) )
4048 size = properties[QStringLiteral( "size" )].toDouble();
4049 if ( properties.contains( QStringLiteral( "angle" ) ) )
4050 angle = properties[QStringLiteral( "angle" )].toDouble();
4051
4052 std::unique_ptr< QgsAnimatedMarkerSymbolLayer > m = std::make_unique< QgsAnimatedMarkerSymbolLayer >( path, size, angle );
4053 m->setFrameRate( properties.value( QStringLiteral( "frameRate" ), QStringLiteral( "10" ) ).toDouble() );
4054
4055 m->setCommonProperties( properties );
4056 return m.release();
4057}
4058
4060{
4061 return QStringLiteral( "AnimatedMarker" );
4062}
4063
4065{
4066 QVariantMap res = QgsRasterMarkerSymbolLayer::properties();
4067 res.insert( QStringLiteral( "frameRate" ), mFrameRateFps );
4068 return res;
4069}
4070
4072{
4073 std::unique_ptr< QgsAnimatedMarkerSymbolLayer > m = std::make_unique< QgsAnimatedMarkerSymbolLayer >( mPath, mSize, mAngle );
4074 m->setFrameRate( mFrameRateFps );
4075 copyCommonProperties( m.get() );
4076 return m.release();
4077}
4078
4080{
4082
4083 mPreparedPaths.clear();
4085 {
4087 mStaticPath = true;
4088 }
4089 else
4090 {
4091 mStaticPath = false;
4092 }
4093}
4094
4095QImage QgsAnimatedMarkerSymbolLayer::fetchImage( QgsRenderContext &context, const QString &path, QSize size, bool preserveAspectRatio, double opacity ) const
4096{
4097 if ( !mStaticPath && !mPreparedPaths.contains( path ) )
4098 {
4100 mPreparedPaths.insert( path );
4101 }
4102
4103 const long long mapFrameNumber = context.currentFrame();
4105 const double markerAnimationDuration = totalFrameCount / mFrameRateFps;
4106
4107 double animationTimeSeconds = 0;
4108 if ( mapFrameNumber >= 0 && context.frameRate() > 0 )
4109 {
4110 // render is part of an animation, so we base the calculated frame on that
4111 animationTimeSeconds = mapFrameNumber / context.frameRate();
4112 }
4113 else
4114 {
4115 // render is outside of animation, so base the calculated frame on the current epoch
4116 animationTimeSeconds = QDateTime::currentMSecsSinceEpoch() / 1000.0;
4117 }
4118
4119 const double markerAnimationProgressSeconds = std::fmod( animationTimeSeconds, markerAnimationDuration );
4120 const int movieFrame = static_cast< int >( std::floor( markerAnimationProgressSeconds * mFrameRateFps ) );
4121
4122 bool cached = false;
4123 return QgsApplication::imageCache()->pathAsImage( path, size, preserveAspectRatio, opacity, cached, context.flags() & Qgis::RenderContextFlag::RenderBlocking, 96, movieFrame );
4124}
4125
@ DynamicRotation
Rotation of symbol may be changed during rendering and symbol should not be cached.
ScaleMethod
Scale methods.
Definition: qgis.h:415
@ ScaleDiameter
Calculate scale by the diameter.
@ ScaleArea
Calculate scale by the area.
MarkerShape
Marker shapes.
Definition: qgis.h:2491
@ 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)
RenderUnit
Rendering size units.
Definition: qgis.h:4255
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size)
@ Millimeters
Millimeters.
@ Unknown
Mixed or unknown units.
@ MapUnits
Map units.
@ MetersInMapUnits
Meters value as Map units.
@ 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...
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Exports QGIS layers to the DXF format.
Definition: qgsdxfexport.h:66
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)
static double mapUnitScaleFactor(double scale, Qgis::RenderUnit symbolUnits, Qgis::DistanceUnit mapUnits, double mapUnitsPerPixel=1.0)
Returns scale factor for conversion to map units.
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)
void writePolygon(const QgsRingSequence &polygon, const QString &layer, const QString &hatchPattern, const QColor &color)
Draw dxf filled polygon (HATCH)
Qgis::DistanceUnit mapUnits() const
Retrieve map units.
double symbologyScale() const
Returns the reference scale for output.
Definition: qgsdxfexport.h:252
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.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol 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.
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.
void setStrokeWidthUnit(Qgis::RenderUnit unit)
Sets the stroke width unit.
~QgsFontMarkerSymbolLayer() override
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.
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 setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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 QFont createFont(const QString &family, int pointSize=-1, int weight=-1, bool italic=false)
Creates a font with the specified family.
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.
static void setFontFamily(QFont &font, const QString &family)
Sets the family for a font object.
Qgis::GeometryType type
Definition: qgsgeometry.h:165
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.
Qgis::RenderUnit mOffsetUnit
Offset units.
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 setOffsetUnit(Qgis::RenderUnit unit)
Sets the units for the symbol's offset.
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.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void setVerticalAnchorPoint(VerticalAnchorPoint v)
Sets the vertical anchor point for positioning the symbol.
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.
Qgis::RenderUnit mSizeUnit
Marker size unit.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units for the symbol's size.
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.
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.
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 final
Returns the calculated value of the property with the specified key from within the collection.