QGIS API Documentation 3.39.0-Master (3aed037ce22)
Loading...
Searching...
No Matches
qgssymbollayerutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssymbollayerutils.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgssymbollayerutils.h"
17
18#include "qgssymbollayer.h"
20#include "qgssymbol.h"
21#include "qgscolorramp.h"
22#include "qgscolorrampimpl.h"
23#include "qgscolorutils.h"
24#include "qgsexpression.h"
25#include "qgsexpressionnode.h"
26#include "qgspainteffect.h"
28#include "qgsapplication.h"
29#include "qgspathresolver.h"
30#include "qgsogcutils.h"
31#include "qgslogger.h"
32#include "qgsreadwritecontext.h"
33#include "qgsrendercontext.h"
34#include "qgsunittypes.h"
37#include "qgsrenderer.h"
38#include "qgsxmlutils.h"
39#include "qgsfillsymbollayer.h"
40#include "qgslinesymbollayer.h"
41#include "qgslinesymbol.h"
42#include "qgsmarkersymbol.h"
43#include "qgsfillsymbol.h"
46#include "qmath.h"
47
48#include <QColor>
49#include <QFont>
50#include <QDomDocument>
51#include <QDomNode>
52#include <QDomElement>
53#include <QIcon>
54#include <QPainter>
55#include <QSettings>
56#include <QPicture>
57#include <QUrl>
58#include <QUrlQuery>
59#include <QMimeData>
60#include <QRegularExpression>
61#include <QDir>
62
63#define POINTS_TO_MM 2.83464567
64
65QString QgsSymbolLayerUtils::encodeColor( const QColor &color )
66{
67 return QStringLiteral( "%1,%2,%3,%4" ).arg( color.red() ).arg( color.green() ).arg( color.blue() ).arg( color.alpha() );
68}
69
70QColor QgsSymbolLayerUtils::decodeColor( const QString &str )
71{
72 const QStringList lst = str.split( ',' );
73 if ( lst.count() < 3 )
74 {
75 return QColor( str );
76 }
77 int red, green, blue, alpha;
78 red = lst[0].toInt();
79 green = lst[1].toInt();
80 blue = lst[2].toInt();
81 alpha = 255;
82 if ( lst.count() > 3 )
83 {
84 alpha = lst[3].toInt();
85 }
86 return QColor( red, green, blue, alpha );
87}
88
90{
91 return QString::number( alpha / 255.0, 'g', 2 );
92}
93
95{
96 bool ok;
97 double alpha = str.toDouble( &ok );
98 if ( !ok || alpha > 1 )
99 alpha = 255;
100 else if ( alpha < 0 )
101 alpha = 0;
102 return alpha * 255;
103}
104
105QString QgsSymbolLayerUtils::encodeSldFontStyle( QFont::Style style )
106{
107 switch ( style )
108 {
109 case QFont::StyleNormal:
110 return QStringLiteral( "normal" );
111 case QFont::StyleItalic:
112 return QStringLiteral( "italic" );
113 case QFont::StyleOblique:
114 return QStringLiteral( "oblique" );
115 default:
116 return QString();
117 }
118}
119
120QFont::Style QgsSymbolLayerUtils::decodeSldFontStyle( const QString &str )
121{
122 if ( str == QLatin1String( "normal" ) ) return QFont::StyleNormal;
123 if ( str == QLatin1String( "italic" ) ) return QFont::StyleItalic;
124 if ( str == QLatin1String( "oblique" ) ) return QFont::StyleOblique;
125 return QFont::StyleNormal;
126}
127
129{
130 if ( weight == 50 ) return QStringLiteral( "normal" );
131 if ( weight == 75 ) return QStringLiteral( "bold" );
132
133 // QFont::Weight is between 0 and 99
134 // CSS font-weight is between 100 and 900
135 if ( weight < 0 ) return QStringLiteral( "100" );
136 if ( weight > 99 ) return QStringLiteral( "900" );
137 return QString::number( weight * 800 / 99 + 100 );
138}
139
141{
142 bool ok;
143 const int weight = str.toInt( &ok );
144 if ( !ok )
145 return static_cast< int >( QFont::Normal );
146
147 // CSS font-weight is between 100 and 900
148 // QFont::Weight is between 0 and 99
149 if ( weight > 900 ) return 99;
150 if ( weight < 100 ) return 0;
151 return ( weight - 100 ) * 99 / 800;
152}
153
154QString QgsSymbolLayerUtils::encodePenStyle( Qt::PenStyle style )
155{
156 switch ( style )
157 {
158 case Qt::NoPen:
159 return QStringLiteral( "no" );
160 case Qt::SolidLine:
161 return QStringLiteral( "solid" );
162 case Qt::DashLine:
163 return QStringLiteral( "dash" );
164 case Qt::DotLine:
165 return QStringLiteral( "dot" );
166 case Qt::DashDotLine:
167 return QStringLiteral( "dash dot" );
168 case Qt::DashDotDotLine:
169 return QStringLiteral( "dash dot dot" );
170 default:
171 return QStringLiteral( "???" );
172 }
173}
174
175Qt::PenStyle QgsSymbolLayerUtils::decodePenStyle( const QString &str )
176{
177 if ( str == QLatin1String( "no" ) ) return Qt::NoPen;
178 if ( str == QLatin1String( "solid" ) ) return Qt::SolidLine;
179 if ( str == QLatin1String( "dash" ) ) return Qt::DashLine;
180 if ( str == QLatin1String( "dot" ) ) return Qt::DotLine;
181 if ( str == QLatin1String( "dash dot" ) ) return Qt::DashDotLine;
182 if ( str == QLatin1String( "dash dot dot" ) ) return Qt::DashDotDotLine;
183 return Qt::SolidLine;
184}
185
186QString QgsSymbolLayerUtils::encodePenJoinStyle( Qt::PenJoinStyle style )
187{
188 switch ( style )
189 {
190 case Qt::BevelJoin:
191 return QStringLiteral( "bevel" );
192 case Qt::MiterJoin:
193 return QStringLiteral( "miter" );
194 case Qt::RoundJoin:
195 return QStringLiteral( "round" );
196 default:
197 return QStringLiteral( "???" );
198 }
199}
200
201Qt::PenJoinStyle QgsSymbolLayerUtils::decodePenJoinStyle( const QString &str )
202{
203 const QString cleaned = str.toLower().trimmed();
204 if ( cleaned == QLatin1String( "bevel" ) )
205 return Qt::BevelJoin;
206 if ( cleaned == QLatin1String( "miter" ) )
207 return Qt::MiterJoin;
208 if ( cleaned == QLatin1String( "round" ) )
209 return Qt::RoundJoin;
210 return Qt::BevelJoin;
211}
212
213QString QgsSymbolLayerUtils::encodeSldLineJoinStyle( Qt::PenJoinStyle style )
214{
215 switch ( style )
216 {
217 case Qt::BevelJoin:
218 return QStringLiteral( "bevel" );
219 case Qt::MiterJoin:
220 return QStringLiteral( "mitre" ); //#spellok
221 case Qt::RoundJoin:
222 return QStringLiteral( "round" );
223 default:
224 return QString();
225 }
226}
227
228Qt::PenJoinStyle QgsSymbolLayerUtils::decodeSldLineJoinStyle( const QString &str )
229{
230 if ( str == QLatin1String( "bevel" ) ) return Qt::BevelJoin;
231 if ( str == QLatin1String( "mitre" ) ) return Qt::MiterJoin; //#spellok
232 if ( str == QLatin1String( "round" ) ) return Qt::RoundJoin;
233 return Qt::BevelJoin;
234}
235
236QString QgsSymbolLayerUtils::encodePenCapStyle( Qt::PenCapStyle style )
237{
238 switch ( style )
239 {
240 case Qt::SquareCap:
241 return QStringLiteral( "square" );
242 case Qt::FlatCap:
243 return QStringLiteral( "flat" );
244 case Qt::RoundCap:
245 return QStringLiteral( "round" );
246 default:
247 return QStringLiteral( "???" );
248 }
249}
250
251Qt::PenCapStyle QgsSymbolLayerUtils::decodePenCapStyle( const QString &str )
252{
253 if ( str == QLatin1String( "square" ) ) return Qt::SquareCap;
254 if ( str == QLatin1String( "flat" ) ) return Qt::FlatCap;
255 if ( str == QLatin1String( "round" ) ) return Qt::RoundCap;
256 return Qt::SquareCap;
257}
258
259QString QgsSymbolLayerUtils::encodeSldLineCapStyle( Qt::PenCapStyle style )
260{
261 switch ( style )
262 {
263 case Qt::SquareCap:
264 return QStringLiteral( "square" );
265 case Qt::FlatCap:
266 return QStringLiteral( "butt" );
267 case Qt::RoundCap:
268 return QStringLiteral( "round" );
269 default:
270 return QString();
271 }
272}
273
274Qt::PenCapStyle QgsSymbolLayerUtils::decodeSldLineCapStyle( const QString &str )
275{
276 if ( str == QLatin1String( "square" ) ) return Qt::SquareCap;
277 if ( str == QLatin1String( "butt" ) ) return Qt::FlatCap;
278 if ( str == QLatin1String( "round" ) ) return Qt::RoundCap;
279 return Qt::SquareCap;
280}
281
282QString QgsSymbolLayerUtils::encodeBrushStyle( Qt::BrushStyle style )
283{
284 switch ( style )
285 {
286 case Qt::SolidPattern :
287 return QStringLiteral( "solid" );
288 case Qt::HorPattern :
289 return QStringLiteral( "horizontal" );
290 case Qt::VerPattern :
291 return QStringLiteral( "vertical" );
292 case Qt::CrossPattern :
293 return QStringLiteral( "cross" );
294 case Qt::BDiagPattern :
295 return QStringLiteral( "b_diagonal" );
296 case Qt::FDiagPattern :
297 return QStringLiteral( "f_diagonal" );
298 case Qt::DiagCrossPattern :
299 return QStringLiteral( "diagonal_x" );
300 case Qt::Dense1Pattern :
301 return QStringLiteral( "dense1" );
302 case Qt::Dense2Pattern :
303 return QStringLiteral( "dense2" );
304 case Qt::Dense3Pattern :
305 return QStringLiteral( "dense3" );
306 case Qt::Dense4Pattern :
307 return QStringLiteral( "dense4" );
308 case Qt::Dense5Pattern :
309 return QStringLiteral( "dense5" );
310 case Qt::Dense6Pattern :
311 return QStringLiteral( "dense6" );
312 case Qt::Dense7Pattern :
313 return QStringLiteral( "dense7" );
314 case Qt::NoBrush :
315 return QStringLiteral( "no" );
316 default:
317 return QStringLiteral( "???" );
318 }
319}
320
321Qt::BrushStyle QgsSymbolLayerUtils::decodeBrushStyle( const QString &str )
322{
323 if ( str == QLatin1String( "solid" ) ) return Qt::SolidPattern;
324 if ( str == QLatin1String( "horizontal" ) ) return Qt::HorPattern;
325 if ( str == QLatin1String( "vertical" ) ) return Qt::VerPattern;
326 if ( str == QLatin1String( "cross" ) ) return Qt::CrossPattern;
327 if ( str == QLatin1String( "b_diagonal" ) ) return Qt::BDiagPattern;
328 if ( str == QLatin1String( "f_diagonal" ) ) return Qt::FDiagPattern;
329 if ( str == QLatin1String( "diagonal_x" ) ) return Qt::DiagCrossPattern;
330 if ( str == QLatin1String( "dense1" ) ) return Qt::Dense1Pattern;
331 if ( str == QLatin1String( "dense2" ) ) return Qt::Dense2Pattern;
332 if ( str == QLatin1String( "dense3" ) ) return Qt::Dense3Pattern;
333 if ( str == QLatin1String( "dense4" ) ) return Qt::Dense4Pattern;
334 if ( str == QLatin1String( "dense5" ) ) return Qt::Dense5Pattern;
335 if ( str == QLatin1String( "dense6" ) ) return Qt::Dense6Pattern;
336 if ( str == QLatin1String( "dense7" ) ) return Qt::Dense7Pattern;
337 if ( str == QLatin1String( "no" ) ) return Qt::NoBrush;
338 return Qt::SolidPattern;
339}
340
341QString QgsSymbolLayerUtils::encodeSldBrushStyle( Qt::BrushStyle style )
342{
343 switch ( style )
344 {
345 case Qt::CrossPattern:
346 return QStringLiteral( "cross" );
347 case Qt::DiagCrossPattern:
348 return QStringLiteral( "x" );
349
350 /* The following names are taken from the presentation "GeoServer
351 * Cartographic Rendering" by Andrea Aime at the FOSS4G 2010.
352 * (see http://2010.foss4g.org/presentations/3588.pdf)
353 */
354 case Qt::HorPattern:
355 return QStringLiteral( "horline" );
356 case Qt::VerPattern:
357 return QStringLiteral( "line" );
358 case Qt::BDiagPattern:
359 return QStringLiteral( "slash" );
360 case Qt::FDiagPattern:
361 return QStringLiteral( "backslash" );
362
363 /* define the other names following the same pattern used above */
364 case Qt::Dense1Pattern:
365 case Qt::Dense2Pattern:
366 case Qt::Dense3Pattern:
367 case Qt::Dense4Pattern:
368 case Qt::Dense5Pattern:
369 case Qt::Dense6Pattern:
370 case Qt::Dense7Pattern:
371 return QStringLiteral( "brush://%1" ).arg( encodeBrushStyle( style ) );
372
373 default:
374 return QString();
375 }
376}
377
378Qt::BrushStyle QgsSymbolLayerUtils::decodeSldBrushStyle( const QString &str )
379{
380 if ( str == QLatin1String( "horline" ) ) return Qt::HorPattern;
381 if ( str == QLatin1String( "line" ) ) return Qt::VerPattern;
382 if ( str == QLatin1String( "cross" ) ) return Qt::CrossPattern;
383 if ( str == QLatin1String( "slash" ) ) return Qt::BDiagPattern;
384 if ( str == QLatin1String( "backshash" ) ) return Qt::FDiagPattern;
385 if ( str == QLatin1String( "x" ) ) return Qt::DiagCrossPattern;
386
387 if ( str.startsWith( QLatin1String( "brush://" ) ) )
388 return decodeBrushStyle( str.mid( 8 ) );
389
390 return Qt::NoBrush;
391}
392
394{
395 const QString compareString = string.trimmed();
396 if ( ok )
397 *ok = true;
398
399 if ( compareString.compare( QLatin1String( "feature" ), Qt::CaseInsensitive ) == 0 )
401 else if ( compareString.compare( QLatin1String( "viewport" ), Qt::CaseInsensitive ) == 0 )
403
404 if ( ok )
405 *ok = false;
407}
408
410{
411 switch ( coordinateReference )
412 {
414 return QStringLiteral( "feature" );
416 return QStringLiteral( "viewport" );
417 }
418 return QString(); // no warnings
419}
420
422{
423 if ( ok )
424 *ok = true;
425
426 bool intOk = false;
427 const QString s = value.toString().toLower().trimmed();
428 if ( s == QLatin1String( "single" ) )
430 else if ( s == QLatin1String( "reversed" ) )
432 else if ( s == QLatin1String( "double" ) )
434 else if ( value.toInt() == 1 )
436 else if ( value.toInt() == 2 )
438 else if ( value.toInt( &intOk ) == 0 && intOk )
440
441 if ( ok )
442 *ok = false;
444}
445
447{
448 if ( ok )
449 *ok = true;
450
451 bool intOk = false;
452 const QString s = value.toString().toLower().trimmed();
453 if ( s == QLatin1String( "plain" ) )
455 else if ( s == QLatin1String( "lefthalf" ) )
457 else if ( s == QLatin1String( "righthalf" ) )
459 else if ( value.toInt() == 1 )
461 else if ( value.toInt() == 2 )
463 else if ( value.toInt( &intOk ) == 0 && intOk )
465
466 if ( ok )
467 *ok = false;
469}
470
472{
473 const QString compareString = string.trimmed();
474 if ( ok )
475 *ok = true;
476
477 if ( compareString.compare( QLatin1String( "no" ), Qt::CaseInsensitive ) == 0 )
479 else if ( compareString.compare( QLatin1String( "shape" ), Qt::CaseInsensitive ) == 0 )
481 else if ( compareString.compare( QLatin1String( "centroid_within" ), Qt::CaseInsensitive ) == 0 )
483 else if ( compareString.compare( QLatin1String( "completely_within" ), Qt::CaseInsensitive ) == 0 )
485
486 if ( ok )
487 *ok = false;
489}
490
492{
493 switch ( mode )
494 {
496 return QStringLiteral( "no" );
498 return QStringLiteral( "shape" );
500 return QStringLiteral( "centroid_within" );
502 return QStringLiteral( "completely_within" );
503 }
504 return QString(); // no warnings
505}
506
508{
509 const QString compareString = string.trimmed();
510 if ( ok )
511 *ok = true;
512
513 if ( compareString.compare( QLatin1String( "no" ), Qt::CaseInsensitive ) == 0 )
515 else if ( compareString.compare( QLatin1String( "during_render" ), Qt::CaseInsensitive ) == 0 )
517 else if ( compareString.compare( QLatin1String( "before_render" ), Qt::CaseInsensitive ) == 0 )
519
520 if ( ok )
521 *ok = false;
523}
524
526{
527 switch ( mode )
528 {
530 return QStringLiteral( "no" );
532 return QStringLiteral( "during_render" );
534 return QStringLiteral( "before_render" );
535 }
536 return QString(); // no warnings
537}
538
539QString QgsSymbolLayerUtils::encodePoint( QPointF point )
540{
541 return QStringLiteral( "%1,%2" ).arg( qgsDoubleToString( point.x() ), qgsDoubleToString( point.y() ) );
542}
543
544QPointF QgsSymbolLayerUtils::decodePoint( const QString &str )
545{
546 QStringList lst = str.split( ',' );
547 if ( lst.count() != 2 )
548 return QPointF( 0, 0 );
549 return QPointF( lst[0].toDouble(), lst[1].toDouble() );
550}
551
552QPointF QgsSymbolLayerUtils::toPoint( const QVariant &value, bool *ok )
553{
554 if ( ok )
555 *ok = false;
556
557 if ( QgsVariantUtils::isNull( value ) )
558 return QPoint();
559
560 if ( value.userType() == QMetaType::Type::QVariantList )
561 {
562 const QVariantList list = value.toList();
563 if ( list.size() != 2 )
564 {
565 return QPointF();
566 }
567 bool convertOk = false;
568 const double x = list.at( 0 ).toDouble( &convertOk );
569 if ( convertOk )
570 {
571 const double y = list.at( 1 ).toDouble( &convertOk );
572 if ( convertOk )
573 {
574 if ( ok )
575 *ok = true;
576 return QPointF( x, y );
577 }
578 }
579 return QPointF();
580 }
581 else
582 {
583 // can't use decodePoint here -- has no OK handling
584 const QStringList list = value.toString().trimmed().split( ',' );
585 if ( list.count() != 2 )
586 return QPointF();
587 bool convertOk = false;
588 const double x = list.at( 0 ).toDouble( &convertOk );
589 if ( convertOk )
590 {
591 const double y = list.at( 1 ).toDouble( &convertOk );
592 if ( convertOk )
593 {
594 if ( ok )
595 *ok = true;
596 return QPointF( x, y );
597 }
598 }
599 return QPointF();
600 }
601}
602
604{
605 return QStringLiteral( "%1,%2" ).arg( qgsDoubleToString( size.width() ), qgsDoubleToString( size.height() ) );
606}
607
608QSizeF QgsSymbolLayerUtils::decodeSize( const QString &string )
609{
610 QStringList lst = string.split( ',' );
611 if ( lst.count() != 2 )
612 return QSizeF( 0, 0 );
613 return QSizeF( lst[0].toDouble(), lst[1].toDouble() );
614}
615
616QSizeF QgsSymbolLayerUtils::toSize( const QVariant &value, bool *ok )
617{
618 if ( ok )
619 *ok = false;
620
621 if ( QgsVariantUtils::isNull( value ) )
622 return QSizeF();
623
624 if ( value.userType() == QMetaType::Type::QVariantList )
625 {
626 const QVariantList list = value.toList();
627 if ( list.size() != 2 )
628 {
629 return QSizeF();
630 }
631 bool convertOk = false;
632 const double x = list.at( 0 ).toDouble( &convertOk );
633 if ( convertOk )
634 {
635 const double y = list.at( 1 ).toDouble( &convertOk );
636 if ( convertOk )
637 {
638 if ( ok )
639 *ok = true;
640 return QSizeF( x, y );
641 }
642 }
643 return QSizeF();
644 }
645 else
646 {
647 // can't use decodePoint here -- has no OK handling
648 const QStringList list = value.toString().trimmed().split( ',' );
649 if ( list.count() != 2 )
650 return QSizeF();
651 bool convertOk = false;
652 const double x = list.at( 0 ).toDouble( &convertOk );
653 if ( convertOk )
654 {
655 const double y = list.at( 1 ).toDouble( &convertOk );
656 if ( convertOk )
657 {
658 if ( ok )
659 *ok = true;
660 return QSizeF( x, y );
661 }
662 }
663 return QSizeF();
664 }
665}
666
668{
669 return QStringLiteral( "3x:%1,%2,%3,%4,%5,%6" ).arg( qgsDoubleToString( mapUnitScale.minScale ),
670 qgsDoubleToString( mapUnitScale.maxScale ) )
671 .arg( mapUnitScale.minSizeMMEnabled ? 1 : 0 )
672 .arg( mapUnitScale.minSizeMM )
673 .arg( mapUnitScale.maxSizeMMEnabled ? 1 : 0 )
674 .arg( mapUnitScale.maxSizeMM );
675}
676
678{
679 QStringList lst;
680 bool v3 = false;
681 if ( str.startsWith( QLatin1String( "3x:" ) ) )
682 {
683 v3 = true;
684 const QString chopped = str.mid( 3 );
685 lst = chopped.split( ',' );
686 }
687 else
688 {
689 lst = str.split( ',' );
690 }
691 if ( lst.count() < 2 )
692 return QgsMapUnitScale();
693
694 double minScale = lst[0].toDouble();
695 if ( !v3 )
696 minScale = minScale != 0 ? 1.0 / minScale : 0;
697 double maxScale = lst[1].toDouble();
698 if ( !v3 )
699 maxScale = maxScale != 0 ? 1.0 / maxScale : 0;
700
701 if ( lst.count() < 6 )
702 {
703 // old format
704 return QgsMapUnitScale( minScale, maxScale );
705 }
706
707 QgsMapUnitScale s( minScale, maxScale );
708 s.minSizeMMEnabled = lst[2].toInt();
709 s.minSizeMM = lst[3].toDouble();
710 s.maxSizeMMEnabled = lst[4].toInt();
711 s.maxSizeMM = lst[5].toDouble();
712 return s;
713}
714
715QString QgsSymbolLayerUtils::encodeSldUom( Qgis::RenderUnit unit, double *scaleFactor )
716{
717 switch ( unit )
718 {
720 if ( scaleFactor )
721 *scaleFactor = 0.001; // from millimeters to meters
722 return QStringLiteral( "http://www.opengeospatial.org/se/units/metre" );
723
725 if ( scaleFactor )
726 *scaleFactor = 1.0; // from meters to meters
727 return QStringLiteral( "http://www.opengeospatial.org/se/units/metre" );
728
730 default:
731 // pixel is the SLD default uom. The "standardized rendering pixel
732 // size" is defined to be 0.28mm × 0.28mm (millimeters).
733 if ( scaleFactor )
734 *scaleFactor = 1 / 0.28; // from millimeters to pixels
735
736 // http://www.opengeospatial.org/sld/units/pixel
737 return QString();
738 }
739}
740
741Qgis::RenderUnit QgsSymbolLayerUtils::decodeSldUom( const QString &str, double *scaleFactor )
742{
743 if ( str == QLatin1String( "http://www.opengeospatial.org/se/units/metre" ) )
744 {
745 if ( scaleFactor )
746 *scaleFactor = 1.0; // from meters to meters
748 }
749 else if ( str == QLatin1String( "http://www.opengeospatial.org/se/units/foot" ) )
750 {
751 if ( scaleFactor )
752 *scaleFactor = 0.3048; // from feet to meters
754 }
755 // pixel is the SLD default uom so it's used if no uom attribute is available or
756 // if uom="http://www.opengeospatial.org/se/units/pixel"
757 else
758 {
759 if ( scaleFactor )
760 *scaleFactor = 1.0; // from pixels to pixels
762 }
763}
764
765QString QgsSymbolLayerUtils::encodeRealVector( const QVector<qreal> &v )
766{
767 QString vectorString;
768 QVector<qreal>::const_iterator it = v.constBegin();
769 for ( ; it != v.constEnd(); ++it )
770 {
771 if ( it != v.constBegin() )
772 {
773 vectorString.append( ';' );
774 }
775 vectorString.append( QString::number( *it ) );
776 }
777 return vectorString;
778}
779
780QVector<qreal> QgsSymbolLayerUtils::decodeRealVector( const QString &s )
781{
782 QVector<qreal> resultVector;
783
784 const QStringList realList = s.split( ';' );
785 QStringList::const_iterator it = realList.constBegin();
786 for ( ; it != realList.constEnd(); ++it )
787 {
788 resultVector.append( it->toDouble() );
789 }
790
791 return resultVector;
792}
793
794QString QgsSymbolLayerUtils::encodeSldRealVector( const QVector<qreal> &v )
795{
796 QString vectorString;
797 QVector<qreal>::const_iterator it = v.constBegin();
798 for ( ; it != v.constEnd(); ++it )
799 {
800 if ( it != v.constBegin() )
801 {
802 vectorString.append( ' ' );
803 }
804 vectorString.append( QString::number( *it ) );
805 }
806 return vectorString;
807}
808
809QVector<qreal> QgsSymbolLayerUtils::decodeSldRealVector( const QString &s )
810{
811 QVector<qreal> resultVector;
812
813 const QStringList realList = s.split( ' ' );
814 QStringList::const_iterator it = realList.constBegin();
815 for ( ; it != realList.constEnd(); ++it )
816 {
817 resultVector.append( it->toDouble() );
818 }
819
820 return resultVector;
821}
822
824{
825 QString encodedValue;
826
827 switch ( scaleMethod )
828 {
830 encodedValue = QStringLiteral( "diameter" );
831 break;
833 encodedValue = QStringLiteral( "area" );
834 break;
835 }
836 return encodedValue;
837}
838
840{
841 Qgis::ScaleMethod scaleMethod;
842
843 if ( str == QLatin1String( "diameter" ) )
844 {
846 }
847 else
848 {
849 scaleMethod = Qgis::ScaleMethod::ScaleArea;
850 }
851
852 return scaleMethod;
853}
854
855QPainter::CompositionMode QgsSymbolLayerUtils::decodeBlendMode( const QString &s )
856{
857 if ( s.compare( QLatin1String( "Lighten" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Lighten;
858 if ( s.compare( QLatin1String( "Screen" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Screen;
859 if ( s.compare( QLatin1String( "Dodge" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_ColorDodge;
860 if ( s.compare( QLatin1String( "Addition" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Plus;
861 if ( s.compare( QLatin1String( "Darken" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Darken;
862 if ( s.compare( QLatin1String( "Multiply" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Multiply;
863 if ( s.compare( QLatin1String( "Burn" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_ColorBurn;
864 if ( s.compare( QLatin1String( "Overlay" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Overlay;
865 if ( s.compare( QLatin1String( "SoftLight" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_SoftLight;
866 if ( s.compare( QLatin1String( "HardLight" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_HardLight;
867 if ( s.compare( QLatin1String( "Difference" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Difference;
868 if ( s.compare( QLatin1String( "Subtract" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Exclusion;
869 return QPainter::CompositionMode_SourceOver; // "Normal"
870}
871
872QIcon QgsSymbolLayerUtils::symbolPreviewIcon( const QgsSymbol *symbol, QSize size, int padding, QgsLegendPatchShape *shape, const QgsScreenProperties &screen )
873{
874 return QIcon( symbolPreviewPixmap( symbol, size, padding, nullptr, false, nullptr, shape, screen ) );
875}
876
877QPixmap QgsSymbolLayerUtils::symbolPreviewPixmap( const QgsSymbol *symbol, QSize size, int padding, QgsRenderContext *customContext, bool selected, const QgsExpressionContext *expressionContext, const QgsLegendPatchShape *shape, const QgsScreenProperties &screen )
878{
879 Q_ASSERT( symbol );
880
881 const double devicePixelRatio = screen.isValid() ? screen.devicePixelRatio() : 1;
882 QPixmap pixmap( size * devicePixelRatio );
883 pixmap.setDevicePixelRatio( devicePixelRatio );
884
885 pixmap.fill( Qt::transparent );
886 QPainter painter;
887 painter.begin( &pixmap );
888 if ( customContext )
889 customContext->setPainterFlagsUsingContext( &painter );
890 else
891 {
892 painter.setRenderHint( QPainter::Antialiasing );
893 painter.setRenderHint( QPainter::SmoothPixmapTransform );
894 }
895
896 if ( customContext )
897 {
898 customContext->setPainter( &painter );
899 }
900
901 if ( padding > 0 )
902 {
903 size.setWidth( size.rwidth() - ( padding * 2 ) );
904 size.setHeight( size.rheight() - ( padding * 2 ) );
905 painter.translate( padding, padding );
906 }
907
908 // If the context has no feature and there are DD properties,
909 // use a clone and clear some DDs: see issue #19096
910 // Applying a data defined size to a categorized layer hides its category symbol in the layers panel and legend
911 if ( symbol->hasDataDefinedProperties() &&
912 !( customContext
913 && customContext->expressionContext().hasFeature( ) ) )
914 {
915 std::unique_ptr<QgsSymbol> symbol_noDD( symbol->clone( ) );
916 const QgsSymbolLayerList layers( symbol_noDD->symbolLayers() );
917 for ( const auto &layer : layers )
918 {
919 for ( int i = 0; i < layer->dataDefinedProperties().count(); ++i )
920 {
921 QgsProperty &prop = layer->dataDefinedProperties().property( i );
922 // don't clear project color properties -- we want to show them in symbol previews
923 if ( prop.isActive() && !prop.isProjectColor() )
924 prop.setActive( false );
925 }
926 }
927 symbol_noDD->drawPreviewIcon( &painter, size, customContext, selected, expressionContext, shape, screen );
928 }
929 else
930 {
931 std::unique_ptr<QgsSymbol> symbolClone( symbol->clone( ) );
932 symbolClone->drawPreviewIcon( &painter, size, customContext, selected, expressionContext, shape, screen );
933 }
934
935 painter.end();
936 return pixmap;
937}
938
940{
941 double maxBleed = 0;
942 for ( int i = 0; i < symbol->symbolLayerCount(); i++ )
943 {
944 QgsSymbolLayer *layer = symbol->symbolLayer( i );
945 const double layerMaxBleed = layer->estimateMaxBleed( context );
946 maxBleed = layerMaxBleed > maxBleed ? layerMaxBleed : maxBleed;
947 }
948
949 return maxBleed;
950}
951
952QPicture QgsSymbolLayerUtils::symbolLayerPreviewPicture( const QgsSymbolLayer *layer, Qgis::RenderUnit units, QSize size, const QgsMapUnitScale &, Qgis::SymbolType parentSymbolType )
953{
954 QPicture picture;
955 QPainter painter;
956 painter.begin( &picture );
957 painter.setRenderHint( QPainter::Antialiasing );
958 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
959 renderContext.setForceVectorOutput( true );
961 renderContext.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
963 renderContext.setPainterFlagsUsingContext( &painter );
964
965 QgsSymbolRenderContext symbolContext( renderContext, units, 1.0, false, Qgis::SymbolRenderHints(), nullptr );
966
967 switch ( parentSymbolType )
968 {
971 break;
974 break;
977 break;
979 break;
980 }
981
982 std::unique_ptr< QgsSymbolLayer > layerClone( layer->clone() );
983 layerClone->drawPreviewIcon( symbolContext, size );
984 painter.end();
985 return picture;
986}
987
988QIcon QgsSymbolLayerUtils::symbolLayerPreviewIcon( const QgsSymbolLayer *layer, Qgis::RenderUnit u, QSize size, const QgsMapUnitScale &, Qgis::SymbolType parentSymbolType, QgsMapLayer *mapLayer, const QgsScreenProperties &screen )
989{
990 const double devicePixelRatio = screen.isValid() ? screen.devicePixelRatio() : 1;
991 QPixmap pixmap( size * devicePixelRatio );
992 pixmap.setDevicePixelRatio( devicePixelRatio );
993 pixmap.fill( Qt::transparent );
994 QPainter painter;
995 painter.begin( &pixmap );
996 painter.setRenderHint( QPainter::Antialiasing );
997 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
998
999 if ( screen.isValid() )
1000 {
1001 screen.updateRenderContextForScreen( renderContext );
1002 }
1003
1006 renderContext.setDevicePixelRatio( devicePixelRatio );
1007 // build a minimal expression context
1008 QgsExpressionContext expContext;
1010 renderContext.setExpressionContext( expContext );
1011
1012 QgsSymbolRenderContext symbolContext( renderContext, u, 1.0, false, Qgis::SymbolRenderHints(), nullptr );
1013
1014 switch ( parentSymbolType )
1015 {
1018 break;
1021 break;
1024 break;
1026 break;
1027 }
1028
1029 std::unique_ptr< QgsSymbolLayer > layerClone( layer->clone() );
1030 layerClone->drawPreviewIcon( symbolContext, size );
1031 painter.end();
1032 return QIcon( pixmap );
1033}
1034
1035QIcon QgsSymbolLayerUtils::colorRampPreviewIcon( QgsColorRamp *ramp, QSize size, int padding )
1036{
1037 return QIcon( colorRampPreviewPixmap( ramp, size, padding ) );
1038}
1039
1040QPixmap QgsSymbolLayerUtils::colorRampPreviewPixmap( QgsColorRamp *ramp, QSize size, int padding, Qt::Orientation direction, bool flipDirection, bool drawTransparentBackground )
1041{
1042 QPixmap pixmap( size );
1043 pixmap.fill( Qt::transparent );
1044 // pixmap.fill( Qt::white ); // this makes the background white instead of transparent
1045 QPainter painter;
1046 painter.begin( &pixmap );
1047
1048 //draw stippled background, for transparent images
1049 if ( drawTransparentBackground )
1050 drawStippledBackground( &painter, QRect( padding, padding, size.width() - padding * 2, size.height() - padding * 2 ) );
1051
1052 // antialiasing makes the colors duller, and no point in antialiasing a color ramp
1053 // painter.setRenderHint( QPainter::Antialiasing );
1054 switch ( direction )
1055 {
1056 case Qt::Horizontal:
1057 {
1058 for ( int i = 0; i < size.width(); i++ )
1059 {
1060 const QPen pen( ramp->color( static_cast< double >( i ) / size.width() ) );
1061 painter.setPen( pen );
1062 const int x = flipDirection ? size.width() - i - 1 : i;
1063 painter.drawLine( x, 0 + padding, x, size.height() - 1 - padding );
1064 }
1065 break;
1066 }
1067
1068 case Qt::Vertical:
1069 {
1070 for ( int i = 0; i < size.height(); i++ )
1071 {
1072 const QPen pen( ramp->color( static_cast< double >( i ) / size.height() ) );
1073 painter.setPen( pen );
1074 const int y = flipDirection ? size.height() - i - 1 : i;
1075 painter.drawLine( 0 + padding, y, size.width() - 1 - padding, y );
1076 }
1077 break;
1078 }
1079 }
1080
1081 painter.end();
1082 return pixmap;
1083}
1084
1085void QgsSymbolLayerUtils::drawStippledBackground( QPainter *painter, QRect rect )
1086{
1087 // create a 2x2 checker-board image
1088 uchar pixDataRGB[] = { 255, 255, 255, 255,
1089 127, 127, 127, 255,
1090 127, 127, 127, 255,
1091 255, 255, 255, 255
1092 };
1093 const QImage img( pixDataRGB, 2, 2, 8, QImage::Format_ARGB32 );
1094 // scale it to rect so at least 5 patterns are shown
1095 const int width = ( rect.width() < rect.height() ) ?
1096 rect.width() / 2.5 : rect.height() / 2.5;
1097 const QPixmap pix = QPixmap::fromImage( img.scaled( width, width ) );
1098 // fill rect with texture
1099 QBrush brush;
1100 brush.setTexture( pix );
1101 painter->fillRect( rect, brush );
1102}
1103
1104void QgsSymbolLayerUtils::drawVertexMarker( double x, double y, QPainter &p, Qgis::VertexMarkerType type, int markerSize )
1105{
1106 const qreal s = ( markerSize - 1 ) / 2.0;
1107
1108 switch ( type )
1109 {
1111 p.setPen( QColor( 50, 100, 120, 200 ) );
1112 p.setBrush( QColor( 200, 200, 210, 120 ) );
1113 p.drawEllipse( x - s, y - s, s * 2, s * 2 );
1114 break;
1116 p.setPen( QColor( 255, 0, 0 ) );
1117 p.drawLine( x - s, y + s, x + s, y - s );
1118 p.drawLine( x - s, y - s, x + s, y + s );
1119 break;
1121 break;
1122 }
1123}
1124
1125#include <QPolygonF>
1126
1127#include <cmath>
1128#include <cfloat>
1129
1130static QPolygonF makeOffsetGeometry( const QgsPolylineXY &polyline )
1131{
1132 int i, pointCount = polyline.count();
1133
1134 QPolygonF resultLine;
1135 resultLine.resize( pointCount );
1136
1137 const QgsPointXY *tempPtr = polyline.data();
1138
1139 for ( i = 0; i < pointCount; ++i, tempPtr++ )
1140 resultLine[i] = QPointF( tempPtr->x(), tempPtr->y() );
1141
1142 return resultLine;
1143}
1144static QList<QPolygonF> makeOffsetGeometry( const QgsPolygonXY &polygon )
1145{
1146 QList<QPolygonF> resultGeom;
1147 resultGeom.reserve( polygon.size() );
1148 for ( int ring = 0; ring < polygon.size(); ++ring )
1149 resultGeom.append( makeOffsetGeometry( polygon[ ring ] ) );
1150 return resultGeom;
1151}
1152
1153QList<QPolygonF> offsetLine( QPolygonF polyline, double dist, Qgis::GeometryType geometryType )
1154{
1155 QList<QPolygonF> resultLine;
1156
1157 if ( polyline.count() < 2 )
1158 {
1159 resultLine.append( polyline );
1160 return resultLine;
1161 }
1162
1163 unsigned int i, pointCount = polyline.count();
1164
1165 QgsPolylineXY tempPolyline( pointCount );
1166 QPointF *tempPtr = polyline.data();
1167 for ( i = 0; i < pointCount; ++i, tempPtr++ )
1168 tempPolyline[i] = QgsPointXY( tempPtr->rx(), tempPtr->ry() );
1169
1170 QgsGeometry tempGeometry = geometryType == Qgis::GeometryType::Polygon ? QgsGeometry::fromPolygonXY( QgsPolygonXY() << tempPolyline ) : QgsGeometry::fromPolylineXY( tempPolyline );
1171 if ( !tempGeometry.isNull() )
1172 {
1173 const int quadSegments = 0; // we want miter joins, not round joins
1174 const double miterLimit = 2.0; // the default value in GEOS (5.0) allows for fairly sharp endings
1175 QgsGeometry offsetGeom;
1176 if ( geometryType == Qgis::GeometryType::Polygon )
1177 offsetGeom = tempGeometry.buffer( -dist, quadSegments, Qgis::EndCapStyle::Flat,
1178 Qgis::JoinStyle::Miter, miterLimit );
1179 else
1180 offsetGeom = tempGeometry.offsetCurve( dist, quadSegments, Qgis::JoinStyle::Miter, miterLimit );
1181
1182 if ( !offsetGeom.isNull() )
1183 {
1184 tempGeometry = offsetGeom;
1185
1186 if ( QgsWkbTypes::flatType( tempGeometry.wkbType() ) == Qgis::WkbType::LineString )
1187 {
1188 const QgsPolylineXY line = tempGeometry.asPolyline();
1189 resultLine.append( makeOffsetGeometry( line ) );
1190 return resultLine;
1191 }
1192 else if ( QgsWkbTypes::flatType( tempGeometry.wkbType() ) == Qgis::WkbType::Polygon )
1193 {
1194 resultLine.append( makeOffsetGeometry( tempGeometry.asPolygon() ) );
1195 return resultLine;
1196 }
1197 else if ( QgsWkbTypes::flatType( tempGeometry.wkbType() ) == Qgis::WkbType::MultiLineString )
1198 {
1199 QgsMultiPolylineXY tempMPolyline = tempGeometry.asMultiPolyline();
1200 resultLine.reserve( tempMPolyline.count() );
1201 for ( int part = 0; part < tempMPolyline.count(); ++part )
1202 {
1203 resultLine.append( makeOffsetGeometry( tempMPolyline[ part ] ) );
1204 }
1205 return resultLine;
1206 }
1207 else if ( QgsWkbTypes::flatType( tempGeometry.wkbType() ) == Qgis::WkbType::MultiPolygon )
1208 {
1209 QgsMultiPolygonXY tempMPolygon = tempGeometry.asMultiPolygon();
1210 resultLine.reserve( tempMPolygon.count() );
1211 for ( int part = 0; part < tempMPolygon.count(); ++part )
1212 {
1213 resultLine.append( makeOffsetGeometry( tempMPolygon[ part ] ) );
1214 }
1215 return resultLine;
1216 }
1217 }
1218 }
1219
1220 // returns original polyline when 'GEOSOffsetCurve' fails!
1221 resultLine.append( polyline );
1222 return resultLine;
1223}
1224
1226
1227
1228QgsSymbol *QgsSymbolLayerUtils::loadSymbol( const QDomElement &element, const QgsReadWriteContext &context )
1229{
1230 if ( element.isNull() )
1231 return nullptr;
1232
1233 QgsSymbolLayerList layers;
1234 QDomNode layerNode = element.firstChild();
1235
1236 while ( !layerNode.isNull() )
1237 {
1238 QDomElement e = layerNode.toElement();
1239 if ( !e.isNull() && e.tagName() != QLatin1String( "data_defined_properties" ) )
1240 {
1241 if ( e.tagName() != QLatin1String( "layer" ) )
1242 {
1243 QgsDebugError( "unknown tag " + e.tagName() );
1244 }
1245 else
1246 {
1247 if ( QgsSymbolLayer *layer = loadSymbolLayer( e, context ) )
1248 {
1249 // Dealing with sub-symbols nested into a layer
1250 const QDomElement s = e.firstChildElement( QStringLiteral( "symbol" ) );
1251 if ( !s.isNull() )
1252 {
1253 std::unique_ptr< QgsSymbol > subSymbol( loadSymbol( s, context ) );
1254 // special handling for SVG fill symbol layer -- upgrade the subsymbol which
1255 // was historically used for the fill stroke to be dedicated symbol layer instead
1256 // in order to match the behavior of all other fill symbol layer types
1257 if ( dynamic_cast< QgsSVGFillSymbolLayer * >( layer ) )
1258 {
1259 // add the SVG fill first
1260 layers.append( layer );
1261 // then add the layers from the subsymbol stroke outline on top
1262 for ( int i = 0; i < subSymbol->symbolLayerCount(); ++i )
1263 {
1264 layers.append( subSymbol->symbolLayer( i )->clone() );
1265 }
1266 }
1267 else
1268 {
1269 const bool res = layer->setSubSymbol( subSymbol.release() );
1270 if ( !res )
1271 {
1272 QgsDebugError( QStringLiteral( "symbol layer refused subsymbol: " ) + s.attribute( "name" ) );
1273 }
1274 layers.append( layer );
1275 }
1276 }
1277 else
1278 {
1279 layers.append( layer );
1280 }
1281 }
1282 }
1283 }
1284 layerNode = layerNode.nextSibling();
1285 }
1286
1287 if ( layers.isEmpty() )
1288 {
1289 QgsDebugError( QStringLiteral( "no layers for symbol" ) );
1290 return nullptr;
1291 }
1292
1293 const QString symbolType = element.attribute( QStringLiteral( "type" ) );
1294
1295 QgsSymbol *symbol = nullptr;
1296 if ( symbolType == QLatin1String( "line" ) )
1297 symbol = new QgsLineSymbol( layers );
1298 else if ( symbolType == QLatin1String( "fill" ) )
1299 symbol = new QgsFillSymbol( layers );
1300 else if ( symbolType == QLatin1String( "marker" ) )
1301 symbol = new QgsMarkerSymbol( layers );
1302 else
1303 {
1304 QgsDebugError( "unknown symbol type " + symbolType );
1305 return nullptr;
1306 }
1307
1308 if ( element.hasAttribute( QStringLiteral( "outputUnit" ) ) )
1309 {
1310 symbol->setOutputUnit( QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "outputUnit" ) ) ) );
1311 }
1312 if ( element.hasAttribute( ( QStringLiteral( "mapUnitScale" ) ) ) )
1313 {
1314 QgsMapUnitScale mapUnitScale;
1315 const double oldMin = element.attribute( QStringLiteral( "mapUnitMinScale" ), QStringLiteral( "0.0" ) ).toDouble();
1316 mapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
1317 const double oldMax = element.attribute( QStringLiteral( "mapUnitMaxScale" ), QStringLiteral( "0.0" ) ).toDouble();
1318 mapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
1319 symbol->setMapUnitScale( mapUnitScale );
1320 }
1321 symbol->setOpacity( element.attribute( QStringLiteral( "alpha" ), QStringLiteral( "1.0" ) ).toDouble() );
1322 symbol->setClipFeaturesToExtent( element.attribute( QStringLiteral( "clip_to_extent" ), QStringLiteral( "1" ) ).toInt() );
1323 symbol->setForceRHR( element.attribute( QStringLiteral( "force_rhr" ), QStringLiteral( "0" ) ).toInt() );
1324 Qgis::SymbolFlags flags;
1325 if ( element.attribute( QStringLiteral( "renderer_should_use_levels" ), QStringLiteral( "0" ) ).toInt() )
1327 symbol->setFlags( flags );
1328
1329 symbol->animationSettings().setIsAnimated( element.attribute( QStringLiteral( "is_animated" ), QStringLiteral( "0" ) ).toInt() );
1330 symbol->animationSettings().setFrameRate( element.attribute( QStringLiteral( "frame_rate" ), QStringLiteral( "10" ) ).toDouble() );
1331
1332 const QDomElement ddProps = element.firstChildElement( QStringLiteral( "data_defined_properties" ) );
1333 if ( !ddProps.isNull() )
1334 {
1336 }
1337
1338 return symbol;
1339}
1340
1342{
1343 const QString layerClass = element.attribute( QStringLiteral( "class" ) );
1344 const bool locked = element.attribute( QStringLiteral( "locked" ) ).toInt();
1345 const bool enabled = element.attribute( QStringLiteral( "enabled" ), QStringLiteral( "1" ) ).toInt();
1346 const int pass = element.attribute( QStringLiteral( "pass" ) ).toInt();
1347 const QString id = element.attribute( QStringLiteral( "id" ) );
1348 const Qgis::SymbolLayerUserFlags userFlags = qgsFlagKeysToValue( element.attribute( QStringLiteral( "userFlags" ) ), Qgis::SymbolLayerUserFlags() );
1349
1350 // parse properties
1351 QVariantMap props = parseProperties( element );
1352
1353 // if there are any paths stored in properties, convert them from relative to absolute
1354 QgsApplication::symbolLayerRegistry()->resolvePaths( layerClass, props, context.pathResolver(), false );
1355
1356 QgsApplication::symbolLayerRegistry()->resolveFonts( layerClass, props, context );
1357
1358 QgsSymbolLayer *layer = nullptr;
1359 layer = QgsApplication::symbolLayerRegistry()->createSymbolLayer( layerClass, props );
1360 if ( layer )
1361 {
1362 layer->setLocked( locked );
1363 layer->setRenderingPass( pass );
1364 layer->setEnabled( enabled );
1365 layer->setUserFlags( userFlags );
1366
1367 // old project format, empty is missing, keep the actual layer one
1368 if ( !id.isEmpty() )
1369 layer->setId( id );
1370
1371 //restore layer effect
1372 const QDomElement effectElem = element.firstChildElement( QStringLiteral( "effect" ) );
1373 if ( !effectElem.isNull() )
1374 {
1375 std::unique_ptr< QgsPaintEffect > effect( QgsApplication::paintEffectRegistry()->createEffect( effectElem ) );
1376 if ( effect && !QgsPaintEffectRegistry::isDefaultStack( effect.get() ) )
1377 layer->setPaintEffect( effect.release() );
1378 }
1379
1380 // restore data defined properties
1381 const QDomElement ddProps = element.firstChildElement( QStringLiteral( "data_defined_properties" ) );
1382 if ( !ddProps.isNull() )
1383 {
1384 const QgsPropertyCollection prevProperties = layer->dataDefinedProperties();
1386
1387 // some symbol layers will be created with data defined properties by default -- we want to retain
1388 // these if they weren't restored from the xml
1389 const QSet< int > oldKeys = prevProperties.propertyKeys();
1390 for ( int key : oldKeys )
1391 {
1392 if ( !layer->dataDefinedProperties().propertyKeys().contains( key ) )
1393 layer->setDataDefinedProperty( static_cast< QgsSymbolLayer::Property >( key ), prevProperties.property( key ) );
1394 }
1395 }
1396
1397 return layer;
1398 }
1399 else
1400 {
1401 QgsDebugError( "unknown class " + layerClass );
1402 return nullptr;
1403 }
1404}
1405
1406static QString _nameForSymbolType( Qgis::SymbolType type )
1407{
1408 switch ( type )
1409 {
1411 return QStringLiteral( "line" );
1413 return QStringLiteral( "marker" );
1415 return QStringLiteral( "fill" );
1416 default:
1417 return QString();
1418 }
1419}
1420
1421QDomElement QgsSymbolLayerUtils::saveSymbol( const QString &name, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context )
1422{
1423 Q_ASSERT( symbol );
1424 QDomElement symEl = doc.createElement( QStringLiteral( "symbol" ) );
1425 symEl.setAttribute( QStringLiteral( "type" ), _nameForSymbolType( symbol->type() ) );
1426 symEl.setAttribute( QStringLiteral( "name" ), name );
1427 symEl.setAttribute( QStringLiteral( "alpha" ), QString::number( symbol->opacity() ) );
1428 symEl.setAttribute( QStringLiteral( "clip_to_extent" ), symbol->clipFeaturesToExtent() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1429 symEl.setAttribute( QStringLiteral( "force_rhr" ), symbol->forceRHR() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1431 symEl.setAttribute( QStringLiteral( "renderer_should_use_levels" ), QStringLiteral( "1" ) );
1432
1433 symEl.setAttribute( QStringLiteral( "is_animated" ), symbol->animationSettings().isAnimated() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1434 symEl.setAttribute( QStringLiteral( "frame_rate" ), qgsDoubleToString( symbol->animationSettings().frameRate() ) );
1435
1436 //QgsDebugMsgLevel( "num layers " + QString::number( symbol->symbolLayerCount() ), 2 );
1437
1438 QDomElement ddProps = doc.createElement( QStringLiteral( "data_defined_properties" ) );
1440 symEl.appendChild( ddProps );
1441
1442 for ( int i = 0; i < symbol->symbolLayerCount(); i++ )
1443 {
1444 const QgsSymbolLayer *layer = symbol->symbolLayer( i );
1445
1446 QDomElement layerEl = doc.createElement( QStringLiteral( "layer" ) );
1447 layerEl.setAttribute( QStringLiteral( "class" ), layer->layerType() );
1448 layerEl.setAttribute( QStringLiteral( "enabled" ), layer->enabled() );
1449 layerEl.setAttribute( QStringLiteral( "locked" ), layer->isLocked() );
1450 layerEl.setAttribute( QStringLiteral( "pass" ), layer->renderingPass() );
1451 layerEl.setAttribute( QStringLiteral( "id" ), layer->id() );
1452 if ( layer->userFlags() != Qgis::SymbolLayerUserFlags() )
1453 layerEl.setAttribute( QStringLiteral( "userFlags" ), qgsFlagValueToKeys( layer->userFlags() ) );
1454
1455 QVariantMap props = layer->properties();
1456
1457 // if there are any paths in properties, convert them from absolute to relative
1458 QgsApplication::symbolLayerRegistry()->resolvePaths( layer->layerType(), props, context.pathResolver(), true );
1459
1460 saveProperties( props, doc, layerEl );
1461
1462 if ( layer->paintEffect() && !QgsPaintEffectRegistry::isDefaultStack( layer->paintEffect() ) )
1463 layer->paintEffect()->saveProperties( doc, layerEl );
1464
1465 QDomElement ddProps = doc.createElement( QStringLiteral( "data_defined_properties" ) );
1467 layerEl.appendChild( ddProps );
1468
1469 if ( const QgsSymbol *subSymbol = const_cast< QgsSymbolLayer * >( layer )->subSymbol() )
1470 {
1471 const QString subname = QStringLiteral( "@%1@%2" ).arg( name ).arg( i );
1472 const QDomElement subEl = saveSymbol( subname, subSymbol, doc, context );
1473 layerEl.appendChild( subEl );
1474 }
1475 symEl.appendChild( layerEl );
1476 }
1477
1478 return symEl;
1479}
1480
1482{
1483 QDomDocument doc( QStringLiteral( "qgis-symbol-definition" ) );
1484 const QDomElement symbolElem = saveSymbol( QStringLiteral( "symbol" ), symbol, doc, QgsReadWriteContext() );
1485 QString props;
1486 QTextStream stream( &props );
1487 symbolElem.save( stream, -1 );
1488 return props;
1489}
1490
1492 Qgis::GeometryType geomType,
1493 QList<QgsSymbolLayer *> &layers )
1494{
1495 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
1496
1497 if ( element.isNull() )
1498 return false;
1499
1500 QgsSymbolLayer *l = nullptr;
1501
1502 const QString symbolizerName = element.localName();
1503
1504 if ( symbolizerName == QLatin1String( "PointSymbolizer" ) )
1505 {
1506 // first check for Graphic element, nothing will be rendered if not found
1507 const QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
1508 if ( graphicElem.isNull() )
1509 {
1510 QgsDebugError( QStringLiteral( "Graphic element not found in PointSymbolizer" ) );
1511 }
1512 else
1513 {
1514 switch ( geomType )
1515 {
1517 // polygon layer and point symbolizer: draw polygon centroid
1518 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "CentroidFill" ), element );
1519 if ( l )
1520 layers.append( l );
1521
1522 break;
1523
1525 // point layer and point symbolizer: use markers
1526 l = createMarkerLayerFromSld( element );
1527 if ( l )
1528 layers.append( l );
1529
1530 break;
1531
1533 // line layer and point symbolizer: draw central point
1534 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SimpleMarker" ), element );
1535 if ( l )
1536 layers.append( l );
1537
1538 break;
1539
1540 default:
1541 break;
1542 }
1543 }
1544 }
1545
1546 if ( symbolizerName == QLatin1String( "LineSymbolizer" ) )
1547 {
1548 // check for Stroke element, nothing will be rendered if not found
1549 const QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
1550 if ( strokeElem.isNull() )
1551 {
1552 QgsDebugError( QStringLiteral( "Stroke element not found in LineSymbolizer" ) );
1553 }
1554 else
1555 {
1556 switch ( geomType )
1557 {
1560 // polygon layer and line symbolizer: draw polygon stroke
1561 // line layer and line symbolizer: draw line
1562 l = createLineLayerFromSld( element );
1563 if ( l )
1564 layers.append( l );
1565
1566 break;
1567
1569 // point layer and line symbolizer: draw a little line marker
1570 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "MarkerLine" ), element );
1571 if ( l )
1572 layers.append( l );
1573
1574 break;
1575
1576 default:
1577 break;
1578 }
1579 }
1580 }
1581
1582 if ( symbolizerName == QLatin1String( "PolygonSymbolizer" ) )
1583 {
1584 // get Fill and Stroke elements, nothing will be rendered if both are missing
1585 const QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
1586 const QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
1587 if ( fillElem.isNull() && strokeElem.isNull() )
1588 {
1589 QgsDebugError( QStringLiteral( "neither Fill nor Stroke element not found in PolygonSymbolizer" ) );
1590 }
1591 else
1592 {
1593 QgsSymbolLayer *l = nullptr;
1594
1595 switch ( geomType )
1596 {
1598 // polygon layer and polygon symbolizer: draw fill
1599
1600 l = createFillLayerFromSld( element );
1601 if ( l )
1602 {
1603 layers.append( l );
1604
1605 // SVGFill and SimpleFill symbolLayerV2 supports stroke internally,
1606 // so don't go forward to create a different symbolLayerV2 for stroke
1607 if ( l->layerType() == QLatin1String( "SimpleFill" ) || l->layerType() == QLatin1String( "SVGFill" ) )
1608 break;
1609 }
1610
1611 // now create polygon stroke
1612 // polygon layer and polygon symbolizer: draw polygon stroke
1613 l = createLineLayerFromSld( element );
1614 if ( l )
1615 layers.append( l );
1616
1617 break;
1618
1620 // line layer and polygon symbolizer: draw line
1621 l = createLineLayerFromSld( element );
1622 if ( l )
1623 layers.append( l );
1624
1625 break;
1626
1628 // point layer and polygon symbolizer: draw a square marker
1629 convertPolygonSymbolizerToPointMarker( element, layers );
1630 break;
1631
1632 default:
1633 break;
1634 }
1635 }
1636 }
1637
1638 return true;
1639}
1640
1642{
1643 const QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
1644 if ( fillElem.isNull() )
1645 {
1646 QgsDebugError( QStringLiteral( "Fill element not found" ) );
1647 return nullptr;
1648 }
1649
1650 QgsSymbolLayer *l = nullptr;
1651
1652 if ( needLinePatternFill( element ) )
1653 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "LinePatternFill" ), element );
1654 else if ( needPointPatternFill( element ) )
1655 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "PointPatternFill" ), element );
1656 else if ( needSvgFill( element ) )
1657 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SVGFill" ), element );
1658 else if ( needRasterImageFill( element ) )
1659 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "RasterFill" ), element );
1660 else
1661 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SimpleFill" ), element );
1662
1663 return l;
1664}
1665
1667{
1668 const QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
1669 if ( strokeElem.isNull() )
1670 {
1671 QgsDebugError( QStringLiteral( "Stroke element not found" ) );
1672 return nullptr;
1673 }
1674
1675 QgsSymbolLayer *l = nullptr;
1676
1677 if ( needMarkerLine( element ) )
1678 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "MarkerLine" ), element );
1679 else
1680 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SimpleLine" ), element );
1681
1682 return l;
1683}
1684
1686{
1687 const QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
1688 if ( graphicElem.isNull() )
1689 {
1690 QgsDebugError( QStringLiteral( "Graphic element not found" ) );
1691 return nullptr;
1692 }
1693
1694 QgsSymbolLayer *l = nullptr;
1695
1696 if ( needFontMarker( element ) )
1697 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "FontMarker" ), element );
1698 else if ( needSvgMarker( element ) )
1699 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SvgMarker" ), element );
1700 else if ( needEllipseMarker( element ) )
1701 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "EllipseMarker" ), element );
1702 else
1703 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SimpleMarker" ), element );
1704
1705 return l;
1706}
1707
1709{
1710 return hasExternalGraphicV2( element, QStringLiteral( "image/svg+xml" ) );
1711}
1712
1713bool QgsSymbolLayerUtils::hasExternalGraphicV2( QDomElement &element, const QString format )
1714{
1715 const QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
1716 if ( graphicElem.isNull() )
1717 return false;
1718
1719 const QDomElement externalGraphicElem = graphicElem.firstChildElement( QStringLiteral( "ExternalGraphic" ) );
1720 if ( externalGraphicElem.isNull() )
1721 return false;
1722
1723 // check for format
1724 const QDomElement formatElem = externalGraphicElem.firstChildElement( QStringLiteral( "Format" ) );
1725 if ( formatElem.isNull() )
1726 return false;
1727
1728 const QString elementFormat = formatElem.firstChild().nodeValue();
1729 if ( ! format.isEmpty() && elementFormat != format )
1730 {
1731 QgsDebugMsgLevel( "unsupported External Graphic format found: " + elementFormat, 4 );
1732 return false;
1733 }
1734
1735 // check for a valid content
1736 const QDomElement onlineResourceElem = externalGraphicElem.firstChildElement( QStringLiteral( "OnlineResource" ) );
1737 const QDomElement inlineContentElem = externalGraphicElem.firstChildElement( QStringLiteral( "InlineContent" ) );
1738 if ( !onlineResourceElem.isNull() )
1739 {
1740 return true;
1741 }
1742#if 0
1743 else if ( !inlineContentElem.isNull() )
1744 {
1745 return false; // not implemented yet
1746 }
1747#endif
1748 else
1749 {
1750 return false;
1751 }
1752}
1753
1754bool QgsSymbolLayerUtils::hasWellKnownMark( QDomElement &element )
1755{
1756 const QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
1757 if ( graphicElem.isNull() )
1758 return false;
1759
1760 const QDomElement markElem = graphicElem.firstChildElement( QStringLiteral( "Mark" ) );
1761 if ( markElem.isNull() )
1762 return false;
1763
1764 const QDomElement wellKnownNameElem = markElem.firstChildElement( QStringLiteral( "WellKnownName" ) );
1765 return !wellKnownNameElem.isNull();
1766}
1767
1768
1769bool QgsSymbolLayerUtils::needFontMarker( QDomElement &element )
1770{
1771 const QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
1772 if ( graphicElem.isNull() )
1773 return false;
1774
1775 const QDomElement markElem = graphicElem.firstChildElement( QStringLiteral( "Mark" ) );
1776 if ( markElem.isNull() )
1777 return false;
1778
1779 // check for format
1780 const QDomElement formatElem = markElem.firstChildElement( QStringLiteral( "Format" ) );
1781 if ( formatElem.isNull() )
1782 return false;
1783
1784 const QString format = formatElem.firstChild().nodeValue();
1785 if ( format != QLatin1String( "ttf" ) )
1786 {
1787 QgsDebugError( "unsupported Graphic Mark format found: " + format );
1788 return false;
1789 }
1790
1791 // check for a valid content
1792 const QDomElement onlineResourceElem = markElem.firstChildElement( QStringLiteral( "OnlineResource" ) );
1793 const QDomElement inlineContentElem = markElem.firstChildElement( QStringLiteral( "InlineContent" ) );
1794 if ( !onlineResourceElem.isNull() )
1795 {
1796 // mark with ttf format has a markIndex element
1797 const QDomElement markIndexElem = markElem.firstChildElement( QStringLiteral( "MarkIndex" ) );
1798 if ( !markIndexElem.isNull() )
1799 return true;
1800 }
1801 else if ( !inlineContentElem.isNull() )
1802 {
1803 return false; // not implemented yet
1804 }
1805
1806 return false;
1807}
1808
1809bool QgsSymbolLayerUtils::needSvgMarker( QDomElement &element )
1810{
1811 return hasExternalGraphicV2( element, QStringLiteral( "image/svg+xml" ) );
1812}
1813
1814bool QgsSymbolLayerUtils::needEllipseMarker( QDomElement &element )
1815{
1816 QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
1817 if ( graphicElem.isNull() )
1818 return false;
1819
1820 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( graphicElem );
1821 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
1822 {
1823 if ( it.key() == QLatin1String( "widthHeightFactor" ) )
1824 {
1825 return true;
1826 }
1827 }
1828
1829 return false;
1830}
1831
1832bool QgsSymbolLayerUtils::needMarkerLine( QDomElement &element )
1833{
1834 const QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
1835 if ( strokeElem.isNull() )
1836 return false;
1837
1838 QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
1839 if ( graphicStrokeElem.isNull() )
1840 return false;
1841
1842 return hasWellKnownMark( graphicStrokeElem );
1843}
1844
1846{
1847 const QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
1848 if ( fillElem.isNull() )
1849 return false;
1850
1851 const QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
1852 if ( graphicFillElem.isNull() )
1853 return false;
1854
1855 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
1856 if ( graphicElem.isNull() )
1857 return false;
1858
1859 // line pattern fill uses horline wellknown marker with an angle
1860
1861 QString name;
1862 QColor fillColor, strokeColor;
1863 double size, strokeWidth;
1864 Qt::PenStyle strokeStyle;
1865 if ( !wellKnownMarkerFromSld( graphicElem, name, fillColor, strokeColor, strokeStyle, strokeWidth, size ) )
1866 return false;
1867
1868 if ( name != QLatin1String( "horline" ) )
1869 return false;
1870
1871 QString angleFunc;
1872 if ( !rotationFromSldElement( graphicElem, angleFunc ) )
1873 return false;
1874
1875 bool ok;
1876 const double angle = angleFunc.toDouble( &ok );
1877 return !( !ok || qgsDoubleNear( angle, 0.0 ) );
1878}
1879
1881{
1882 const QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
1883 if ( fillElem.isNull() )
1884 return false;
1885
1886 const QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
1887 if ( graphicFillElem.isNull() )
1888 return false;
1889
1890 const QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
1891 if ( graphicElem.isNull() )
1892 return false;
1893
1894 const QDomElement markElem = graphicElem.firstChildElement( QStringLiteral( "Mark" ) );
1895 if ( markElem.isNull() )
1896 return false;
1897
1898 return true;
1899}
1900
1901bool QgsSymbolLayerUtils::needSvgFill( QDomElement &element )
1902{
1903 const QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
1904 if ( fillElem.isNull() )
1905 return false;
1906
1907 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
1908 if ( graphicFillElem.isNull() )
1909 return false;
1910
1911 return hasExternalGraphicV2( graphicFillElem, QStringLiteral( "image/svg+xml" ) );
1912}
1913
1915{
1916 const QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
1917 if ( fillElem.isNull() )
1918 return false;
1919
1920 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
1921 if ( graphicFillElem.isNull() )
1922 return false;
1923
1924 return hasExternalGraphicV2( graphicFillElem, QStringLiteral( "image/png" ) ) || hasExternalGraphicV2( graphicFillElem, QStringLiteral( "image/jpeg" ) ) || hasExternalGraphicV2( graphicFillElem, QStringLiteral( "image/gif" ) );
1925}
1926
1927
1928bool QgsSymbolLayerUtils::convertPolygonSymbolizerToPointMarker( QDomElement &element, QList<QgsSymbolLayer *> &layerList )
1929{
1930 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
1931
1932 /* SE 1.1 says about PolygonSymbolizer:
1933 if a point geometry is referenced instead of a polygon,
1934 then a small, square, ortho-normal polygon should be
1935 constructed for rendering.
1936 */
1937
1938 QgsSymbolLayerList layers;
1939
1940 // retrieve both Fill and Stroke elements
1941 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
1942 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
1943
1944 // first symbol layer
1945 {
1946 bool validFill = false, validStroke = false;
1947
1948 // check for simple fill
1949 // Fill element can contain some SvgParameter elements
1950 QColor fillColor;
1951 Qt::BrushStyle fillStyle;
1952
1953 if ( fillFromSld( fillElem, fillStyle, fillColor ) )
1954 validFill = true;
1955
1956 // check for simple stroke
1957 // Stroke element can contain some SvgParameter elements
1958 QColor strokeColor;
1959 Qt::PenStyle strokeStyle;
1960 double strokeWidth = 1.0, dashOffset = 0.0;
1961 QVector<qreal> customDashPattern;
1962
1963 if ( lineFromSld( strokeElem, strokeStyle, strokeColor, strokeWidth,
1964 nullptr, nullptr, &customDashPattern, &dashOffset ) )
1965 validStroke = true;
1966
1967 if ( validFill || validStroke )
1968 {
1969 QVariantMap map;
1970 map[QStringLiteral( "name" )] = QStringLiteral( "square" );
1971 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( validFill ? fillColor : Qt::transparent );
1972 map[QStringLiteral( "color_border" )] = QgsColorUtils::colorToString( validStroke ? strokeColor : Qt::transparent );
1973 map[QStringLiteral( "size" )] = QString::number( 6 );
1974 map[QStringLiteral( "angle" )] = QString::number( 0 );
1975 map[QStringLiteral( "offset" )] = encodePoint( QPointF( 0, 0 ) );
1976 layers.append( QgsApplication::symbolLayerRegistry()->createSymbolLayer( QStringLiteral( "SimpleMarker" ), map ) );
1977 }
1978 }
1979
1980 // second symbol layer
1981 {
1982 bool validFill = false, validStroke = false;
1983
1984 // check for graphic fill
1985 QString name, format;
1986 int markIndex = -1;
1987 QColor fillColor, strokeColor;
1988 double strokeWidth = 1.0, size = 0.0, angle = 0.0;
1989 QPointF offset;
1990
1991 // Fill element can contain a GraphicFill element
1992 const QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
1993 if ( !graphicFillElem.isNull() )
1994 {
1995 // GraphicFill element must contain a Graphic element
1996 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
1997 if ( !graphicElem.isNull() )
1998 {
1999 // Graphic element can contains some ExternalGraphic and Mark element
2000 // search for the first supported one and use it
2001 bool found = false;
2002
2003 const QDomElement graphicChildElem = graphicElem.firstChildElement();
2004 while ( !graphicChildElem.isNull() )
2005 {
2006 if ( graphicChildElem.localName() == QLatin1String( "Mark" ) )
2007 {
2008 // check for a well known name
2009 const QDomElement wellKnownNameElem = graphicChildElem.firstChildElement( QStringLiteral( "WellKnownName" ) );
2010 if ( !wellKnownNameElem.isNull() )
2011 {
2012 name = wellKnownNameElem.firstChild().nodeValue();
2013 found = true;
2014 break;
2015 }
2016 }
2017
2018 if ( graphicChildElem.localName() == QLatin1String( "ExternalGraphic" ) || graphicChildElem.localName() == QLatin1String( "Mark" ) )
2019 {
2020 // check for external graphic format
2021 const QDomElement formatElem = graphicChildElem.firstChildElement( QStringLiteral( "Format" ) );
2022 if ( formatElem.isNull() )
2023 continue;
2024
2025 format = formatElem.firstChild().nodeValue();
2026
2027 // TODO: remove this check when more formats will be supported
2028 // only SVG external graphics are supported in this moment
2029 if ( graphicChildElem.localName() == QLatin1String( "ExternalGraphic" ) && format != QLatin1String( "image/svg+xml" ) )
2030 continue;
2031
2032 // TODO: remove this check when more formats will be supported
2033 // only ttf marks are supported in this moment
2034 if ( graphicChildElem.localName() == QLatin1String( "Mark" ) && format != QLatin1String( "ttf" ) )
2035 continue;
2036
2037 // check for a valid content
2038 const QDomElement onlineResourceElem = graphicChildElem.firstChildElement( QStringLiteral( "OnlineResource" ) );
2039 const QDomElement inlineContentElem = graphicChildElem.firstChildElement( QStringLiteral( "InlineContent" ) );
2040
2041 if ( !onlineResourceElem.isNull() )
2042 {
2043 name = onlineResourceElem.attributeNS( QStringLiteral( "http://www.w3.org/1999/xlink" ), QStringLiteral( "href" ) );
2044
2045 if ( graphicChildElem.localName() == QLatin1String( "Mark" ) && format == QLatin1String( "ttf" ) )
2046 {
2047 // mark with ttf format may have a name like ttf://fontFamily
2048 if ( name.startsWith( QLatin1String( "ttf://" ) ) )
2049 name = name.mid( 6 );
2050
2051 // mark with ttf format has a markIndex element
2052 const QDomElement markIndexElem = graphicChildElem.firstChildElement( QStringLiteral( "MarkIndex" ) );
2053 if ( markIndexElem.isNull() )
2054 continue;
2055
2056 bool ok;
2057 const int v = markIndexElem.firstChild().nodeValue().toInt( &ok );
2058 if ( !ok || v < 0 )
2059 continue;
2060
2061 markIndex = v;
2062 }
2063
2064 found = true;
2065 break;
2066 }
2067#if 0
2068 else if ( !inlineContentElem.isNull() )
2069 continue; // TODO: not implemented yet
2070#endif
2071 else
2072 continue;
2073 }
2074
2075 // if Mark element is present but it doesn't contains neither
2076 // WellKnownName nor OnlineResource nor InlineContent,
2077 // use the default mark (square)
2078 if ( graphicChildElem.localName() == QLatin1String( "Mark" ) )
2079 {
2080 name = QStringLiteral( "square" );
2081 found = true;
2082 break;
2083 }
2084 }
2085
2086 // if found a valid Mark, check for its Fill and Stroke element
2087 if ( found && graphicChildElem.localName() == QLatin1String( "Mark" ) )
2088 {
2089 // XXX: recursive definition!?! couldn't be dangerous???
2090 // to avoid recursion we handle only simple fill and simple stroke
2091
2092 // check for simple fill
2093 // Fill element can contain some SvgParameter elements
2094 Qt::BrushStyle markFillStyle;
2095
2096 QDomElement markFillElem = graphicChildElem.firstChildElement( QStringLiteral( "Fill" ) );
2097 if ( fillFromSld( markFillElem, markFillStyle, fillColor ) )
2098 validFill = true;
2099
2100 // check for simple stroke
2101 // Stroke element can contain some SvgParameter elements
2102 Qt::PenStyle strokeStyle;
2103 double strokeWidth = 1.0, dashOffset = 0.0;
2104 QVector<qreal> customDashPattern;
2105
2106 QDomElement markStrokeElem = graphicChildElem.firstChildElement( QStringLiteral( "Stroke" ) );
2107 if ( lineFromSld( markStrokeElem, strokeStyle, strokeColor, strokeWidth,
2108 nullptr, nullptr, &customDashPattern, &dashOffset ) )
2109 validStroke = true;
2110 }
2111
2112 if ( found )
2113 {
2114 // check for Opacity, Size, Rotation, AnchorPoint, Displacement
2115 const QDomElement opacityElem = graphicElem.firstChildElement( QStringLiteral( "Opacity" ) );
2116 if ( !opacityElem.isNull() )
2117 fillColor.setAlpha( decodeSldAlpha( opacityElem.firstChild().nodeValue() ) );
2118
2119 const QDomElement sizeElem = graphicElem.firstChildElement( QStringLiteral( "Size" ) );
2120 if ( !sizeElem.isNull() )
2121 {
2122 bool ok;
2123 const double v = sizeElem.firstChild().nodeValue().toDouble( &ok );
2124 if ( ok && v > 0 )
2125 size = v;
2126 }
2127
2128 QString angleFunc;
2129 if ( rotationFromSldElement( graphicElem, angleFunc ) && !angleFunc.isEmpty() )
2130 {
2131 bool ok;
2132 const double v = angleFunc.toDouble( &ok );
2133 if ( ok )
2134 angle = v;
2135 }
2136
2137 displacementFromSldElement( graphicElem, offset );
2138 }
2139 }
2140 }
2141
2142 if ( validFill || validStroke )
2143 {
2144 if ( format == QLatin1String( "image/svg+xml" ) )
2145 {
2146 QVariantMap map;
2147 map[QStringLiteral( "name" )] = name;
2148 map[QStringLiteral( "fill" )] = fillColor.name();
2149 map[QStringLiteral( "outline" )] = strokeColor.name();
2150 map[QStringLiteral( "outline-width" )] = QString::number( strokeWidth );
2151 if ( !qgsDoubleNear( size, 0.0 ) )
2152 map[QStringLiteral( "size" )] = QString::number( size );
2153 if ( !qgsDoubleNear( angle, 0.0 ) )
2154 map[QStringLiteral( "angle" )] = QString::number( angle );
2155 if ( !offset.isNull() )
2156 map[QStringLiteral( "offset" )] = encodePoint( offset );
2157 layers.append( QgsApplication::symbolLayerRegistry()->createSymbolLayer( QStringLiteral( "SvgMarker" ), map ) );
2158 }
2159 else if ( format == QLatin1String( "ttf" ) )
2160 {
2161 QVariantMap map;
2162 map[QStringLiteral( "font" )] = name;
2163 map[QStringLiteral( "chr" )] = markIndex;
2164 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( validFill ? fillColor : Qt::transparent );
2165 if ( size > 0 )
2166 map[QStringLiteral( "size" )] = QString::number( size );
2167 if ( !qgsDoubleNear( angle, 0.0 ) )
2168 map[QStringLiteral( "angle" )] = QString::number( angle );
2169 if ( !offset.isNull() )
2170 map[QStringLiteral( "offset" )] = encodePoint( offset );
2171 layers.append( QgsApplication::symbolLayerRegistry()->createSymbolLayer( QStringLiteral( "FontMarker" ), map ) );
2172 }
2173 }
2174 }
2175
2176 if ( layers.isEmpty() )
2177 return false;
2178
2179 layerList << layers;
2180 layers.clear();
2181 return true;
2182}
2183
2184void QgsSymbolLayerUtils::fillToSld( QDomDocument &doc, QDomElement &element, Qt::BrushStyle brushStyle, const QColor &color )
2185{
2186 QString patternName;
2187 switch ( brushStyle )
2188 {
2189 case Qt::NoBrush:
2190 return;
2191
2192 case Qt::SolidPattern:
2193 if ( color.isValid() )
2194 {
2195 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "fill" ), color.name() ) );
2196 if ( color.alpha() < 255 )
2197 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "fill-opacity" ), encodeSldAlpha( color.alpha() ) ) );
2198 }
2199 return;
2200
2201 case Qt::CrossPattern:
2202 case Qt::DiagCrossPattern:
2203 case Qt::HorPattern:
2204 case Qt::VerPattern:
2205 case Qt::BDiagPattern:
2206 case Qt::FDiagPattern:
2207 case Qt::Dense1Pattern:
2208 case Qt::Dense2Pattern:
2209 case Qt::Dense3Pattern:
2210 case Qt::Dense4Pattern:
2211 case Qt::Dense5Pattern:
2212 case Qt::Dense6Pattern:
2213 case Qt::Dense7Pattern:
2214 patternName = encodeSldBrushStyle( brushStyle );
2215 break;
2216
2217 default:
2218 element.appendChild( doc.createComment( QStringLiteral( "Qt::BrushStyle '%1'' not supported yet" ).arg( brushStyle ) ) );
2219 return;
2220 }
2221
2222 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
2223 element.appendChild( graphicFillElem );
2224
2225 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
2226 graphicFillElem.appendChild( graphicElem );
2227
2228 const QColor fillColor = patternName.startsWith( QLatin1String( "brush://" ) ) ? color : QColor();
2229 const QColor strokeColor = !patternName.startsWith( QLatin1String( "brush://" ) ) ? color : QColor();
2230
2231 /* Use WellKnownName tag to handle QT brush styles. */
2232 wellKnownMarkerToSld( doc, graphicElem, patternName, fillColor, strokeColor, Qt::SolidLine, -1, -1 );
2233}
2234
2235bool QgsSymbolLayerUtils::fillFromSld( QDomElement &element, Qt::BrushStyle &brushStyle, QColor &color )
2236{
2237 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2238
2239 brushStyle = Qt::SolidPattern;
2240 color = QColor( 128, 128, 128 );
2241
2242 if ( element.isNull() )
2243 {
2244 brushStyle = Qt::NoBrush;
2245 color = QColor();
2246 return true;
2247 }
2248
2249 const QDomElement graphicFillElem = element.firstChildElement( QStringLiteral( "GraphicFill" ) );
2250 // if no GraphicFill element is found, it's a solid fill
2251 if ( graphicFillElem.isNull() )
2252 {
2253 QgsStringMap svgParams = getSvgParameterList( element );
2254 for ( QgsStringMap::iterator it = svgParams.begin(); it != svgParams.end(); ++it )
2255 {
2256 QgsDebugMsgLevel( QStringLiteral( "found SvgParameter %1: %2" ).arg( it.key(), it.value() ), 2 );
2257
2258 if ( it.key() == QLatin1String( "fill" ) )
2259 color = QColor( it.value() );
2260 else if ( it.key() == QLatin1String( "fill-opacity" ) )
2261 color.setAlpha( decodeSldAlpha( it.value() ) );
2262 }
2263 }
2264 else // wellKnown marker
2265 {
2266 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
2267 if ( graphicElem.isNull() )
2268 return false; // Graphic is required within GraphicFill
2269
2270 QString patternName = QStringLiteral( "square" );
2271 QColor fillColor, strokeColor;
2272 double strokeWidth, size;
2273 Qt::PenStyle strokeStyle;
2274 if ( !wellKnownMarkerFromSld( graphicElem, patternName, fillColor, strokeColor, strokeStyle, strokeWidth, size ) )
2275 return false;
2276
2277 brushStyle = decodeSldBrushStyle( patternName );
2278 if ( brushStyle == Qt::NoBrush )
2279 return false; // unable to decode brush style
2280
2281 const QColor c = patternName.startsWith( QLatin1String( "brush://" ) ) ? fillColor : strokeColor;
2282 if ( c.isValid() )
2283 color = c;
2284 }
2285
2286 return true;
2287}
2288
2289void QgsSymbolLayerUtils::lineToSld( QDomDocument &doc, QDomElement &element,
2290 Qt::PenStyle penStyle, const QColor &color, double width,
2291 const Qt::PenJoinStyle *penJoinStyle, const Qt::PenCapStyle *penCapStyle,
2292 const QVector<qreal> *customDashPattern, double dashOffset )
2293{
2294 QVector<qreal> dashPattern;
2295 const QVector<qreal> *pattern = &dashPattern;
2296
2297 if ( penStyle == Qt::CustomDashLine && !customDashPattern )
2298 {
2299 element.appendChild( doc.createComment( QStringLiteral( "WARNING: Custom dash pattern required but not provided. Using default dash pattern." ) ) );
2300 penStyle = Qt::DashLine;
2301 }
2302
2303 switch ( penStyle )
2304 {
2305 case Qt::NoPen:
2306 return;
2307
2308 case Qt::SolidLine:
2309 break;
2310
2311 case Qt::DashLine:
2312 dashPattern.push_back( 4.0 );
2313 dashPattern.push_back( 2.0 );
2314 break;
2315 case Qt::DotLine:
2316 dashPattern.push_back( 1.0 );
2317 dashPattern.push_back( 2.0 );
2318 break;
2319 case Qt::DashDotLine:
2320 dashPattern.push_back( 4.0 );
2321 dashPattern.push_back( 2.0 );
2322 dashPattern.push_back( 1.0 );
2323 dashPattern.push_back( 2.0 );
2324 break;
2325 case Qt::DashDotDotLine:
2326 dashPattern.push_back( 4.0 );
2327 dashPattern.push_back( 2.0 );
2328 dashPattern.push_back( 1.0 );
2329 dashPattern.push_back( 2.0 );
2330 dashPattern.push_back( 1.0 );
2331 dashPattern.push_back( 2.0 );
2332 break;
2333
2334 case Qt::CustomDashLine:
2335 Q_ASSERT( customDashPattern );
2336 pattern = customDashPattern;
2337 break;
2338
2339 default:
2340 element.appendChild( doc.createComment( QStringLiteral( "Qt::BrushStyle '%1'' not supported yet" ).arg( penStyle ) ) );
2341 return;
2342 }
2343
2344 if ( color.isValid() )
2345 {
2346 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke" ), color.name() ) );
2347 if ( color.alpha() < 255 )
2348 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-opacity" ), encodeSldAlpha( color.alpha() ) ) );
2349 }
2350 if ( width > 0 )
2351 {
2352 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-width" ), qgsDoubleToString( width ) ) );
2353 }
2354 else if ( width == 0 )
2355 {
2356 // hairline, yet not zero. it's actually painted in qgis
2357 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-width" ), QStringLiteral( "0.5" ) ) );
2358 }
2359 if ( penJoinStyle )
2360 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-linejoin" ), encodeSldLineJoinStyle( *penJoinStyle ) ) );
2361 if ( penCapStyle )
2362 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-linecap" ), encodeSldLineCapStyle( *penCapStyle ) ) );
2363
2364 if ( !pattern->isEmpty() )
2365 {
2366 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-dasharray" ), encodeSldRealVector( *pattern ) ) );
2367 if ( !qgsDoubleNear( dashOffset, 0.0 ) )
2368 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-dashoffset" ), qgsDoubleToString( dashOffset ) ) );
2369 }
2370}
2371
2372
2373bool QgsSymbolLayerUtils::lineFromSld( QDomElement &element,
2374 Qt::PenStyle &penStyle, QColor &color, double &width,
2375 Qt::PenJoinStyle *penJoinStyle, Qt::PenCapStyle *penCapStyle,
2376 QVector<qreal> *customDashPattern, double *dashOffset )
2377{
2378 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2379
2380 penStyle = Qt::SolidLine;
2381 color = QColor( 0, 0, 0 );
2382 width = 1;
2383 if ( penJoinStyle )
2384 *penJoinStyle = Qt::BevelJoin;
2385 if ( penCapStyle )
2386 *penCapStyle = Qt::SquareCap;
2387 if ( customDashPattern )
2388 customDashPattern->clear();
2389 if ( dashOffset )
2390 *dashOffset = 0;
2391
2392 if ( element.isNull() )
2393 {
2394 penStyle = Qt::NoPen;
2395 color = QColor();
2396 return true;
2397 }
2398
2399 QgsStringMap svgParams = getSvgParameterList( element );
2400 for ( QgsStringMap::iterator it = svgParams.begin(); it != svgParams.end(); ++it )
2401 {
2402 QgsDebugMsgLevel( QStringLiteral( "found SvgParameter %1: %2" ).arg( it.key(), it.value() ), 2 );
2403
2404 if ( it.key() == QLatin1String( "stroke" ) )
2405 {
2406 color = QColor( it.value() );
2407 }
2408 else if ( it.key() == QLatin1String( "stroke-opacity" ) )
2409 {
2410 color.setAlpha( decodeSldAlpha( it.value() ) );
2411 }
2412 else if ( it.key() == QLatin1String( "stroke-width" ) )
2413 {
2414 bool ok;
2415 const double w = it.value().toDouble( &ok );
2416 if ( ok )
2417 width = w;
2418 }
2419 else if ( it.key() == QLatin1String( "stroke-linejoin" ) && penJoinStyle )
2420 {
2421 *penJoinStyle = decodeSldLineJoinStyle( it.value() );
2422 }
2423 else if ( it.key() == QLatin1String( "stroke-linecap" ) && penCapStyle )
2424 {
2425 *penCapStyle = decodeSldLineCapStyle( it.value() );
2426 }
2427 else if ( it.key() == QLatin1String( "stroke-dasharray" ) )
2428 {
2429 const QVector<qreal> dashPattern = decodeSldRealVector( it.value() );
2430 if ( !dashPattern.isEmpty() )
2431 {
2432 // convert the dasharray to one of the QT pen style,
2433 // if no match is found then set pen style to CustomDashLine
2434 bool dashPatternFound = false;
2435
2436 if ( dashPattern.count() == 2 )
2437 {
2438 if ( dashPattern.at( 0 ) == 4.0 &&
2439 dashPattern.at( 1 ) == 2.0 )
2440 {
2441 penStyle = Qt::DashLine;
2442 dashPatternFound = true;
2443 }
2444 else if ( dashPattern.at( 0 ) == 1.0 &&
2445 dashPattern.at( 1 ) == 2.0 )
2446 {
2447 penStyle = Qt::DotLine;
2448 dashPatternFound = true;
2449 }
2450 }
2451 else if ( dashPattern.count() == 4 )
2452 {
2453 if ( dashPattern.at( 0 ) == 4.0 &&
2454 dashPattern.at( 1 ) == 2.0 &&
2455 dashPattern.at( 2 ) == 1.0 &&
2456 dashPattern.at( 3 ) == 2.0 )
2457 {
2458 penStyle = Qt::DashDotLine;
2459 dashPatternFound = true;
2460 }
2461 }
2462 else if ( dashPattern.count() == 6 )
2463 {
2464 if ( dashPattern.at( 0 ) == 4.0 &&
2465 dashPattern.at( 1 ) == 2.0 &&
2466 dashPattern.at( 2 ) == 1.0 &&
2467 dashPattern.at( 3 ) == 2.0 &&
2468 dashPattern.at( 4 ) == 1.0 &&
2469 dashPattern.at( 5 ) == 2.0 )
2470 {
2471 penStyle = Qt::DashDotDotLine;
2472 dashPatternFound = true;
2473 }
2474 }
2475
2476 // default case: set pen style to CustomDashLine
2477 if ( !dashPatternFound )
2478 {
2479 if ( customDashPattern )
2480 {
2481 penStyle = Qt::CustomDashLine;
2482 *customDashPattern = dashPattern;
2483 }
2484 else
2485 {
2486 QgsDebugMsgLevel( QStringLiteral( "custom dash pattern required but not provided. Using default dash pattern." ), 2 );
2487 penStyle = Qt::DashLine;
2488 }
2489 }
2490 }
2491 }
2492 else if ( it.key() == QLatin1String( "stroke-dashoffset" ) && dashOffset )
2493 {
2494 bool ok;
2495 const double d = it.value().toDouble( &ok );
2496 if ( ok )
2497 *dashOffset = d;
2498 }
2499 }
2500
2501 return true;
2502}
2503
2504void QgsSymbolLayerUtils::externalGraphicToSld( QDomDocument &doc, QDomElement &element,
2505 const QString &path, const QString &mime,
2506 const QColor &color, double size )
2507{
2508 QDomElement externalGraphicElem = doc.createElement( QStringLiteral( "se:ExternalGraphic" ) );
2509 element.appendChild( externalGraphicElem );
2510
2511 createOnlineResourceElement( doc, externalGraphicElem, path, mime );
2512
2513 //TODO: missing a way to handle svg color. Should use <se:ColorReplacement>
2514 Q_UNUSED( color )
2515
2516 if ( size >= 0 )
2517 {
2518 QDomElement sizeElem = doc.createElement( QStringLiteral( "se:Size" ) );
2519 sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
2520 element.appendChild( sizeElem );
2521 }
2522}
2523
2524void QgsSymbolLayerUtils::parametricSvgToSld( QDomDocument &doc, QDomElement &graphicElem,
2525 const QString &path, const QColor &fillColor, double size, const QColor &strokeColor, double strokeWidth )
2526{
2527 // Parametric SVG paths are an extension that few systems will understand, but se:Graphic allows for fallback
2528 // symbols, this encodes the full parametric path first, the pure shape second, and a mark with the right colors as
2529 // a last resort for systems that cannot do SVG at all
2530
2531 // encode parametric version with all coloring details (size is going to be encoded by the last fallback)
2532 graphicElem.appendChild( doc.createComment( QStringLiteral( "Parametric SVG" ) ) );
2533 const QString parametricPath = getSvgParametricPath( path, fillColor, strokeColor, strokeWidth );
2534 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, parametricPath, QStringLiteral( "image/svg+xml" ), fillColor, -1 );
2535 // also encode a fallback version without parameters, in case a renderer gets confused by the parameters
2536 graphicElem.appendChild( doc.createComment( QStringLiteral( "Plain SVG fallback, no parameters" ) ) );
2537 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, path, QStringLiteral( "image/svg+xml" ), fillColor, -1 );
2538 // finally encode a simple mark with the right colors/outlines for renderers that cannot do SVG at all
2539 graphicElem.appendChild( doc.createComment( QStringLiteral( "Well known marker fallback" ) ) );
2540 QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, QStringLiteral( "square" ), fillColor, strokeColor, Qt::PenStyle::SolidLine, strokeWidth, -1 );
2541
2542 // size is encoded here, it's part of se:Graphic, not attached to the single symbol
2543 if ( size >= 0 )
2544 {
2545 QDomElement sizeElem = doc.createElement( QStringLiteral( "se:Size" ) );
2546 sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
2547 graphicElem.appendChild( sizeElem );
2548 }
2549}
2550
2551
2552QString QgsSymbolLayerUtils::getSvgParametricPath( const QString &basePath, const QColor &fillColor, const QColor &strokeColor, double strokeWidth )
2553{
2554 QUrlQuery url;
2555 if ( fillColor.isValid() )
2556 {
2557 url.addQueryItem( QStringLiteral( "fill" ), fillColor.name() );
2558 url.addQueryItem( QStringLiteral( "fill-opacity" ), encodeSldAlpha( fillColor.alpha() ) );
2559 }
2560 else
2561 {
2562 url.addQueryItem( QStringLiteral( "fill" ), QStringLiteral( "#000000" ) );
2563 url.addQueryItem( QStringLiteral( "fill-opacity" ), QStringLiteral( "1" ) );
2564 }
2565 if ( strokeColor.isValid() )
2566 {
2567 url.addQueryItem( QStringLiteral( "outline" ), strokeColor.name() );
2568 url.addQueryItem( QStringLiteral( "outline-opacity" ), encodeSldAlpha( strokeColor.alpha() ) );
2569 }
2570 else
2571 {
2572 url.addQueryItem( QStringLiteral( "outline" ), QStringLiteral( "#000000" ) );
2573 url.addQueryItem( QStringLiteral( "outline-opacity" ), QStringLiteral( "1" ) );
2574 }
2575 url.addQueryItem( QStringLiteral( "outline-width" ), QString::number( strokeWidth ) );
2576 const QString params = url.toString( QUrl::FullyEncoded );
2577 if ( params.isEmpty() )
2578 {
2579 return basePath;
2580 }
2581 else
2582 {
2583 return basePath + "?" + params;
2584 }
2585}
2586
2588 QString &path, QString &mime,
2589 QColor &color, double &size )
2590{
2591 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2592 Q_UNUSED( color )
2593
2594 QDomElement externalGraphicElem = element.firstChildElement( QStringLiteral( "ExternalGraphic" ) );
2595 if ( externalGraphicElem.isNull() )
2596 return false;
2597
2598 onlineResourceFromSldElement( externalGraphicElem, path, mime );
2599
2600 const QDomElement sizeElem = element.firstChildElement( QStringLiteral( "Size" ) );
2601 if ( !sizeElem.isNull() )
2602 {
2603 bool ok;
2604 const double s = sizeElem.firstChild().nodeValue().toDouble( &ok );
2605 if ( ok )
2606 size = s;
2607 }
2608
2609 return true;
2610}
2611
2612void QgsSymbolLayerUtils::externalMarkerToSld( QDomDocument &doc, QDomElement &element,
2613 const QString &path, const QString &format, int *markIndex,
2614 const QColor &color, double size )
2615{
2616 QDomElement markElem = doc.createElement( QStringLiteral( "se:Mark" ) );
2617 element.appendChild( markElem );
2618
2619 createOnlineResourceElement( doc, markElem, path, format );
2620
2621 if ( markIndex )
2622 {
2623 QDomElement markIndexElem = doc.createElement( QStringLiteral( "se:MarkIndex" ) );
2624 markIndexElem.appendChild( doc.createTextNode( QString::number( *markIndex ) ) );
2625 markElem.appendChild( markIndexElem );
2626 }
2627
2628 // <Fill>
2629 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
2630 fillToSld( doc, fillElem, Qt::SolidPattern, color );
2631 markElem.appendChild( fillElem );
2632
2633 // <Size>
2634 if ( !qgsDoubleNear( size, 0.0 ) && size > 0 )
2635 {
2636 QDomElement sizeElem = doc.createElement( QStringLiteral( "se:Size" ) );
2637 sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
2638 element.appendChild( sizeElem );
2639 }
2640}
2641
2643 QString &path, QString &format, int &markIndex,
2644 QColor &color, double &size )
2645{
2646 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2647
2648 color = QColor();
2649 markIndex = -1;
2650 size = -1;
2651
2652 QDomElement markElem = element.firstChildElement( QStringLiteral( "Mark" ) );
2653 if ( markElem.isNull() )
2654 return false;
2655
2656 onlineResourceFromSldElement( markElem, path, format );
2657
2658 const QDomElement markIndexElem = markElem.firstChildElement( QStringLiteral( "MarkIndex" ) );
2659 if ( !markIndexElem.isNull() )
2660 {
2661 bool ok;
2662 const int i = markIndexElem.firstChild().nodeValue().toInt( &ok );
2663 if ( ok )
2664 markIndex = i;
2665 }
2666
2667 // <Fill>
2668 QDomElement fillElem = markElem.firstChildElement( QStringLiteral( "Fill" ) );
2669 Qt::BrushStyle b = Qt::SolidPattern;
2670 fillFromSld( fillElem, b, color );
2671 // ignore brush style, solid expected
2672
2673 // <Size>
2674 const QDomElement sizeElem = element.firstChildElement( QStringLiteral( "Size" ) );
2675 if ( !sizeElem.isNull() )
2676 {
2677 bool ok;
2678 const double s = sizeElem.firstChild().nodeValue().toDouble( &ok );
2679 if ( ok )
2680 size = s;
2681 }
2682
2683 return true;
2684}
2685
2686void QgsSymbolLayerUtils::wellKnownMarkerToSld( QDomDocument &doc, QDomElement &element,
2687 const QString &name, const QColor &color, const QColor &strokeColor, Qt::PenStyle strokeStyle,
2688 double strokeWidth, double size )
2689{
2690 QDomElement markElem = doc.createElement( QStringLiteral( "se:Mark" ) );
2691 element.appendChild( markElem );
2692
2693 QDomElement wellKnownNameElem = doc.createElement( QStringLiteral( "se:WellKnownName" ) );
2694 wellKnownNameElem.appendChild( doc.createTextNode( name ) );
2695 markElem.appendChild( wellKnownNameElem );
2696
2697 // <Fill>
2698 if ( color.isValid() )
2699 {
2700 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
2701 fillToSld( doc, fillElem, Qt::SolidPattern, color );
2702 markElem.appendChild( fillElem );
2703 }
2704
2705 // <Stroke>
2706 if ( strokeColor.isValid() )
2707 {
2708 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
2709 lineToSld( doc, strokeElem, strokeStyle, strokeColor, strokeWidth );
2710 markElem.appendChild( strokeElem );
2711 }
2712
2713 // <Size>
2714 if ( !qgsDoubleNear( size, 0.0 ) && size > 0 )
2715 {
2716 QDomElement sizeElem = doc.createElement( QStringLiteral( "se:Size" ) );
2717 sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
2718 element.appendChild( sizeElem );
2719 }
2720}
2721
2723 QString &name, QColor &color, QColor &strokeColor, Qt::PenStyle &strokeStyle,
2724 double &strokeWidth, double &size )
2725{
2726 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
2727
2728 name = QStringLiteral( "square" );
2729 color = QColor();
2730 strokeColor = QColor( 0, 0, 0 );
2731 strokeWidth = 1;
2732 size = 6;
2733
2734 const QDomElement markElem = element.firstChildElement( QStringLiteral( "Mark" ) );
2735 if ( markElem.isNull() )
2736 return false;
2737
2738 const QDomElement wellKnownNameElem = markElem.firstChildElement( QStringLiteral( "WellKnownName" ) );
2739 if ( !wellKnownNameElem.isNull() )
2740 {
2741 name = wellKnownNameElem.firstChild().nodeValue();
2742 QgsDebugMsgLevel( "found Mark with well known name: " + name, 2 );
2743 }
2744
2745 // <Fill>
2746 QDomElement fillElem = markElem.firstChildElement( QStringLiteral( "Fill" ) );
2747 Qt::BrushStyle b = Qt::SolidPattern;
2748 fillFromSld( fillElem, b, color );
2749 // ignore brush style, solid expected
2750
2751 // <Stroke>
2752 QDomElement strokeElem = markElem.firstChildElement( QStringLiteral( "Stroke" ) );
2753 lineFromSld( strokeElem, strokeStyle, strokeColor, strokeWidth );
2754 // ignore stroke style, solid expected
2755
2756 // <Size>
2757 const QDomElement sizeElem = element.firstChildElement( QStringLiteral( "Size" ) );
2758 if ( !sizeElem.isNull() )
2759 {
2760 bool ok;
2761 const double s = sizeElem.firstChild().nodeValue().toDouble( &ok );
2762 if ( ok )
2763 size = s;
2764 }
2765
2766 return true;
2767}
2768
2769void QgsSymbolLayerUtils::createRotationElement( QDomDocument &doc, QDomElement &element, const QString &rotationFunc )
2770{
2771 if ( !rotationFunc.isEmpty() )
2772 {
2773 QDomElement rotationElem = doc.createElement( QStringLiteral( "se:Rotation" ) );
2774 createExpressionElement( doc, rotationElem, rotationFunc );
2775 element.appendChild( rotationElem );
2776 }
2777}
2778
2779bool QgsSymbolLayerUtils::rotationFromSldElement( QDomElement &element, QString &rotationFunc )
2780{
2781 QDomElement rotationElem = element.firstChildElement( QStringLiteral( "Rotation" ) );
2782 if ( !rotationElem.isNull() )
2783 {
2784 return functionFromSldElement( rotationElem, rotationFunc );
2785 }
2786 return true;
2787}
2788
2789
2790void QgsSymbolLayerUtils::createOpacityElement( QDomDocument &doc, QDomElement &element, const QString &alphaFunc )
2791{
2792 if ( !alphaFunc.isEmpty() )
2793 {
2794 QDomElement opacityElem = doc.createElement( QStringLiteral( "se:Opacity" ) );
2795 createExpressionElement( doc, opacityElem, alphaFunc );
2796 element.appendChild( opacityElem );
2797 }
2798}
2799
2800bool QgsSymbolLayerUtils::opacityFromSldElement( QDomElement &element, QString &alphaFunc )
2801{
2802 QDomElement opacityElem = element.firstChildElement( QStringLiteral( "Opacity" ) );
2803 if ( !opacityElem.isNull() )
2804 {
2805 return functionFromSldElement( opacityElem, alphaFunc );
2806 }
2807 return true;
2808}
2809
2810void QgsSymbolLayerUtils::createDisplacementElement( QDomDocument &doc, QDomElement &element, QPointF offset )
2811{
2812 if ( offset.isNull() )
2813 return;
2814
2815 QDomElement displacementElem = doc.createElement( QStringLiteral( "se:Displacement" ) );
2816 element.appendChild( displacementElem );
2817
2818 QDomElement dispXElem = doc.createElement( QStringLiteral( "se:DisplacementX" ) );
2819 dispXElem.appendChild( doc.createTextNode( qgsDoubleToString( offset.x(), 2 ) ) );
2820
2821 QDomElement dispYElem = doc.createElement( QStringLiteral( "se:DisplacementY" ) );
2822 dispYElem.appendChild( doc.createTextNode( qgsDoubleToString( offset.y(), 2 ) ) );
2823
2824 displacementElem.appendChild( dispXElem );
2825 displacementElem.appendChild( dispYElem );
2826}
2827
2828void QgsSymbolLayerUtils::createAnchorPointElement( QDomDocument &doc, QDomElement &element, QPointF anchor )
2829{
2830 // anchor is not tested for null, (0,0) is _not_ the default value (0.5, 0) is.
2831
2832 QDomElement anchorElem = doc.createElement( QStringLiteral( "se:AnchorPoint" ) );
2833 element.appendChild( anchorElem );
2834
2835 QDomElement anchorXElem = doc.createElement( QStringLiteral( "se:AnchorPointX" ) );
2836 anchorXElem.appendChild( doc.createTextNode( qgsDoubleToString( anchor.x() ) ) );
2837
2838 QDomElement anchorYElem = doc.createElement( QStringLiteral( "se:AnchorPointY" ) );
2839 anchorYElem.appendChild( doc.createTextNode( qgsDoubleToString( anchor.y() ) ) );
2840
2841 anchorElem.appendChild( anchorXElem );
2842 anchorElem.appendChild( anchorYElem );
2843}
2844
2845bool QgsSymbolLayerUtils::displacementFromSldElement( QDomElement &element, QPointF &offset )
2846{
2847 offset = QPointF( 0, 0 );
2848
2849 const QDomElement displacementElem = element.firstChildElement( QStringLiteral( "Displacement" ) );
2850 if ( displacementElem.isNull() )
2851 return true;
2852
2853 const QDomElement dispXElem = displacementElem.firstChildElement( QStringLiteral( "DisplacementX" ) );
2854 if ( !dispXElem.isNull() )
2855 {
2856 bool ok;
2857 const double offsetX = dispXElem.firstChild().nodeValue().toDouble( &ok );
2858 if ( ok )
2859 offset.setX( offsetX );
2860 }
2861
2862 const QDomElement dispYElem = displacementElem.firstChildElement( QStringLiteral( "DisplacementY" ) );
2863 if ( !dispYElem.isNull() )
2864 {
2865 bool ok;
2866 const double offsetY = dispYElem.firstChild().nodeValue().toDouble( &ok );
2867 if ( ok )
2868 offset.setY( offsetY );
2869 }
2870
2871 return true;
2872}
2873
2874void QgsSymbolLayerUtils::labelTextToSld( QDomDocument &doc, QDomElement &element,
2875 const QString &label, const QFont &font,
2876 const QColor &color, double size )
2877{
2878 QDomElement labelElem = doc.createElement( QStringLiteral( "se:Label" ) );
2879 labelElem.appendChild( doc.createTextNode( label ) );
2880 element.appendChild( labelElem );
2881
2882 QDomElement fontElem = doc.createElement( QStringLiteral( "se:Font" ) );
2883 element.appendChild( fontElem );
2884
2885 fontElem.appendChild( createSvgParameterElement( doc, QStringLiteral( "font-family" ), font.family() ) );
2886#if 0
2887 fontElem.appendChild( createSldParameterElement( doc, "font-style", encodeSldFontStyle( font.style() ) ) );
2888 fontElem.appendChild( createSldParameterElement( doc, "font-weight", encodeSldFontWeight( font.weight() ) ) );
2889#endif
2890 fontElem.appendChild( createSvgParameterElement( doc, QStringLiteral( "font-size" ), QString::number( size ) ) );
2891
2892 // <Fill>
2893 if ( color.isValid() )
2894 {
2895 QDomElement fillElem = doc.createElement( QStringLiteral( "Fill" ) );
2896 fillToSld( doc, fillElem, Qt::SolidPattern, color );
2897 element.appendChild( fillElem );
2898 }
2899}
2900
2901QString QgsSymbolLayerUtils::ogrFeatureStylePen( double width, double mmScaleFactor, double mapUnitScaleFactor, const QColor &c,
2902 Qt::PenJoinStyle joinStyle,
2903 Qt::PenCapStyle capStyle,
2904 double offset,
2905 const QVector<qreal> *dashPattern )
2906{
2907 QString penStyle;
2908 penStyle.append( "PEN(" );
2909 penStyle.append( "c:" );
2910 penStyle.append( c.name() );
2911 penStyle.append( ",w:" );
2912 //dxf driver writes ground units as mm? Should probably be changed in ogr
2913 penStyle.append( QString::number( width * mmScaleFactor ) );
2914 penStyle.append( "mm" );
2915
2916 //dash dot vector
2917 if ( dashPattern && !dashPattern->isEmpty() )
2918 {
2919 penStyle.append( ",p:\"" );
2920 QVector<qreal>::const_iterator pIt = dashPattern->constBegin();
2921 for ( ; pIt != dashPattern->constEnd(); ++pIt )
2922 {
2923 if ( pIt != dashPattern->constBegin() )
2924 {
2925 penStyle.append( ' ' );
2926 }
2927 penStyle.append( QString::number( *pIt * mapUnitScaleFactor ) );
2928 penStyle.append( 'g' );
2929 }
2930 penStyle.append( '\"' );
2931 }
2932
2933 //cap
2934 penStyle.append( ",cap:" );
2935 switch ( capStyle )
2936 {
2937 case Qt::SquareCap:
2938 penStyle.append( 'p' );
2939 break;
2940 case Qt::RoundCap:
2941 penStyle.append( 'r' );
2942 break;
2943 case Qt::FlatCap:
2944 default:
2945 penStyle.append( 'b' );
2946 }
2947
2948 //join
2949 penStyle.append( ",j:" );
2950 switch ( joinStyle )
2951 {
2952 case Qt::BevelJoin:
2953 penStyle.append( 'b' );
2954 break;
2955 case Qt::RoundJoin:
2956 penStyle.append( 'r' );
2957 break;
2958 case Qt::MiterJoin:
2959 default:
2960 penStyle.append( 'm' );
2961 }
2962
2963 //offset
2964 if ( !qgsDoubleNear( offset, 0.0 ) )
2965 {
2966 penStyle.append( ",dp:" );
2967 penStyle.append( QString::number( offset * mapUnitScaleFactor ) );
2968 penStyle.append( 'g' );
2969 }
2970
2971 penStyle.append( ')' );
2972 return penStyle;
2973}
2974
2975QString QgsSymbolLayerUtils::ogrFeatureStyleBrush( const QColor &fillColor )
2976{
2977 QString brushStyle;
2978 brushStyle.append( "BRUSH(" );
2979 brushStyle.append( "fc:" );
2980 brushStyle.append( fillColor.name() );
2981 brushStyle.append( ')' );
2982 return brushStyle;
2983}
2984
2985void QgsSymbolLayerUtils::createGeometryElement( QDomDocument &doc, QDomElement &element, const QString &geomFunc )
2986{
2987 if ( geomFunc.isEmpty() )
2988 return;
2989
2990 QDomElement geometryElem = doc.createElement( QStringLiteral( "Geometry" ) );
2991 element.appendChild( geometryElem );
2992
2993 /* About using a function within the Geometry tag.
2994 *
2995 * The SLD specification <= 1.1 is vague:
2996 * "In principle, a fixed geometry could be defined using GML or
2997 * operators could be defined for computing the geometry from
2998 * references or literals. However, using a feature property directly
2999 * is by far the most commonly useful method."
3000 *
3001 * Even if it seems that specs should take care all the possible cases,
3002 * looking at the XML schema fragment that encodes the Geometry element,
3003 * it has to be a PropertyName element:
3004 * <xsd:element name="Geometry">
3005 * <xsd:complexType>
3006 * <xsd:sequence>
3007 * <xsd:element ref="ogc:PropertyName"/>
3008 * </xsd:sequence>
3009 * </xsd:complexType>
3010 * </xsd:element>
3011 *
3012 * Anyway we will use a ogc:Function to handle geometry transformations
3013 * like offset, centroid, ...
3014 */
3015
3016 createExpressionElement( doc, geometryElem, geomFunc );
3017}
3018
3019bool QgsSymbolLayerUtils::geometryFromSldElement( QDomElement &element, QString &geomFunc )
3020{
3021 QDomElement geometryElem = element.firstChildElement( QStringLiteral( "Geometry" ) );
3022 if ( geometryElem.isNull() )
3023 return true;
3024
3025 return functionFromSldElement( geometryElem, geomFunc );
3026}
3027
3028bool QgsSymbolLayerUtils::createExpressionElement( QDomDocument &doc, QDomElement &element, const QString &function )
3029{
3030 // let's use QgsExpression to generate the SLD for the function
3031 const QgsExpression expr( function );
3032 if ( expr.hasParserError() )
3033 {
3034 element.appendChild( doc.createComment( "Parser Error: " + expr.parserErrorString() + " - Expression was: " + function ) );
3035 return false;
3036 }
3037 const QDomElement filterElem = QgsOgcUtils::expressionToOgcExpression( expr, doc );
3038 if ( !filterElem.isNull() )
3039 element.appendChild( filterElem );
3040 return true;
3041}
3042
3043
3044bool QgsSymbolLayerUtils::createFunctionElement( QDomDocument &doc, QDomElement &element, const QString &function )
3045{
3046 // else rule is not a valid expression
3047 if ( function == QLatin1String( "ELSE" ) )
3048 {
3049 const QDomElement filterElem = QgsOgcUtils::elseFilterExpression( doc );
3050 element.appendChild( filterElem );
3051 return true;
3052 }
3053 else
3054 {
3055 // let's use QgsExpression to generate the SLD for the function
3056 const QgsExpression expr( function );
3057 if ( expr.hasParserError() )
3058 {
3059 element.appendChild( doc.createComment( "Parser Error: " + expr.parserErrorString() + " - Expression was: " + function ) );
3060 return false;
3061 }
3062 const QDomElement filterElem = QgsOgcUtils::expressionToOgcFilter( expr, doc );
3063 if ( !filterElem.isNull() )
3064 element.appendChild( filterElem );
3065 return true;
3066 }
3067}
3068
3069bool QgsSymbolLayerUtils::functionFromSldElement( QDomElement &element, QString &function )
3070{
3071 // check if ogc:Filter or contains ogc:Filters
3072 QDomElement elem = element;
3073 if ( element.tagName() != QLatin1String( "Filter" ) )
3074 {
3075 const QDomNodeList filterNodes = element.elementsByTagName( QStringLiteral( "Filter" ) );
3076 if ( !filterNodes.isEmpty() )
3077 {
3078 elem = filterNodes.at( 0 ).toElement();
3079 }
3080 }
3081
3082 if ( elem.isNull() )
3083 {
3084 return false;
3085 }
3086
3087 // parse ogc:Filter
3089 if ( !expr )
3090 return false;
3091
3092 const bool valid = !expr->hasParserError();
3093 if ( !valid )
3094 {
3095 QgsDebugError( "parser error: " + expr->parserErrorString() );
3096 }
3097 else
3098 {
3099 function = expr->expression();
3100 }
3101
3102 delete expr;
3103 return valid;
3104}
3105
3106void QgsSymbolLayerUtils::createOnlineResourceElement( QDomDocument &doc, QDomElement &element,
3107 const QString &path, const QString &format )
3108{
3109 // get resource url or relative path
3110 const QString url = svgSymbolPathToName( path, QgsPathResolver() );
3111 QDomElement onlineResourceElem = doc.createElement( QStringLiteral( "se:OnlineResource" ) );
3112 onlineResourceElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
3113 onlineResourceElem.setAttribute( QStringLiteral( "xlink:href" ), url );
3114 element.appendChild( onlineResourceElem );
3115
3116 QDomElement formatElem = doc.createElement( QStringLiteral( "se:Format" ) );
3117 formatElem.appendChild( doc.createTextNode( format ) );
3118 element.appendChild( formatElem );
3119}
3120
3121bool QgsSymbolLayerUtils::onlineResourceFromSldElement( QDomElement &element, QString &path, QString &format )
3122{
3123 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
3124
3125 const QDomElement onlineResourceElem = element.firstChildElement( QStringLiteral( "OnlineResource" ) );
3126 if ( onlineResourceElem.isNull() )
3127 return false;
3128
3129 path = QUrl::fromPercentEncoding( onlineResourceElem.attributeNS( QStringLiteral( "http://www.w3.org/1999/xlink" ), QStringLiteral( "href" ) ).toUtf8() );
3130
3131 const QDomElement formatElem = element.firstChildElement( QStringLiteral( "Format" ) );
3132 if ( formatElem.isNull() )
3133 return false; // OnlineResource requires a Format sibling element
3134
3135 format = formatElem.firstChild().nodeValue();
3136 return true;
3137}
3138
3139
3140QDomElement QgsSymbolLayerUtils::createSvgParameterElement( QDomDocument &doc, const QString &name, const QString &value )
3141{
3142 QDomElement nodeElem = doc.createElement( QStringLiteral( "se:SvgParameter" ) );
3143 nodeElem.setAttribute( QStringLiteral( "name" ), name );
3144 nodeElem.appendChild( doc.createTextNode( value ) );
3145 return nodeElem;
3146}
3147
3149{
3150 QgsStringMap params;
3151 QString value;
3152
3153 QDomElement paramElem = element.firstChildElement();
3154 while ( !paramElem.isNull() )
3155 {
3156 if ( paramElem.localName() == QLatin1String( "SvgParameter" ) || paramElem.localName() == QLatin1String( "CssParameter" ) )
3157 {
3158 const QString name = paramElem.attribute( QStringLiteral( "name" ) );
3159 if ( paramElem.firstChild().nodeType() == QDomNode::TextNode )
3160 {
3161 value = paramElem.firstChild().nodeValue();
3162 }
3163 else
3164 {
3165 if ( paramElem.firstChild().nodeType() == QDomNode::ElementNode &&
3166 paramElem.firstChild().localName() == QLatin1String( "Literal" ) )
3167 {
3168 QgsDebugMsgLevel( paramElem.firstChild().localName(), 3 );
3169 value = paramElem.firstChild().firstChild().nodeValue();
3170 }
3171 else
3172 {
3173 QgsDebugError( QStringLiteral( "unexpected child of %1" ).arg( paramElem.localName() ) );
3174 }
3175 }
3176
3177 if ( !name.isEmpty() && !value.isEmpty() )
3178 params[ name ] = value;
3179 }
3180
3181 paramElem = paramElem.nextSiblingElement();
3182 }
3183
3184 return params;
3185}
3186
3187QDomElement QgsSymbolLayerUtils::createVendorOptionElement( QDomDocument &doc, const QString &name, const QString &value )
3188{
3189 QDomElement nodeElem = doc.createElement( QStringLiteral( "se:VendorOption" ) );
3190 nodeElem.setAttribute( QStringLiteral( "name" ), name );
3191 nodeElem.appendChild( doc.createTextNode( value ) );
3192 return nodeElem;
3193}
3194
3196{
3197 QgsStringMap params;
3198
3199 QDomElement paramElem = element.firstChildElement( QStringLiteral( "VendorOption" ) );
3200 while ( !paramElem.isNull() )
3201 {
3202 const QString name = paramElem.attribute( QStringLiteral( "name" ) );
3203 const QString value = paramElem.firstChild().nodeValue();
3204
3205 if ( !name.isEmpty() && !value.isEmpty() )
3206 params[ name ] = value;
3207
3208 paramElem = paramElem.nextSiblingElement( QStringLiteral( "VendorOption" ) );
3209 }
3210
3211 return params;
3212}
3213
3214
3215QVariantMap QgsSymbolLayerUtils::parseProperties( const QDomElement &element )
3216{
3217 const QVariant newSymbols = QgsXmlUtils::readVariant( element.firstChildElement( QStringLiteral( "Option" ) ) );
3218 if ( newSymbols.userType() == QMetaType::Type::QVariantMap )
3219 {
3220 return newSymbols.toMap();
3221 }
3222 else
3223 {
3224 // read old style of writing properties
3225 // backward compatibility with project saved in <= 3.16
3226 QVariantMap props;
3227 QDomElement e = element.firstChildElement();
3228 while ( !e.isNull() )
3229 {
3230 if ( e.tagName() == QLatin1String( "prop" ) )
3231 {
3232 const QString propKey = e.attribute( QStringLiteral( "k" ) );
3233 const QString propValue = e.attribute( QStringLiteral( "v" ) );
3234 props[propKey] = propValue;
3235 }
3236 e = e.nextSiblingElement();
3237 }
3238 return props;
3239 }
3240}
3241
3242
3243void QgsSymbolLayerUtils::saveProperties( QVariantMap props, QDomDocument &doc, QDomElement &element )
3244{
3245 element.appendChild( QgsXmlUtils::writeVariant( props, doc ) );
3246}
3247
3249{
3250 // go through symbols one-by-one and load them
3251
3252 QgsSymbolMap symbols;
3253 QDomElement e = element.firstChildElement();
3254
3255 while ( !e.isNull() )
3256 {
3257 if ( e.tagName() == QLatin1String( "symbol" ) )
3258 {
3259 QgsSymbol *symbol = QgsSymbolLayerUtils::loadSymbol( e, context );
3260 if ( symbol )
3261 symbols.insert( e.attribute( QStringLiteral( "name" ) ), symbol );
3262 }
3263 else
3264 {
3265 QgsDebugError( "unknown tag: " + e.tagName() );
3266 }
3267 e = e.nextSiblingElement();
3268 }
3269
3270
3271 // now walk through the list of symbols and find those prefixed with @
3272 // these symbols are sub-symbols of some other symbol layers
3273 // e.g. symbol named "@foo@1" is sub-symbol of layer 1 in symbol "foo"
3274 QStringList subsymbols;
3275
3276 for ( QMap<QString, QgsSymbol *>::iterator it = symbols.begin(); it != symbols.end(); ++it )
3277 {
3278 if ( it.key()[0] != '@' )
3279 continue;
3280
3281 // add to array (for deletion)
3282 subsymbols.append( it.key() );
3283
3284 QStringList parts = it.key().split( '@' );
3285 if ( parts.count() < 3 )
3286 {
3287 QgsDebugError( "found subsymbol with invalid name: " + it.key() );
3288 delete it.value(); // we must delete it
3289 continue; // some invalid syntax
3290 }
3291 const QString symname = parts[1];
3292 const int symlayer = parts[2].toInt();
3293
3294 if ( !symbols.contains( symname ) )
3295 {
3296 QgsDebugError( "subsymbol references invalid symbol: " + symname );
3297 delete it.value(); // we must delete it
3298 continue;
3299 }
3300
3301 QgsSymbol *sym = symbols[symname];
3302 if ( symlayer < 0 || symlayer >= sym->symbolLayerCount() )
3303 {
3304 QgsDebugError( "subsymbol references invalid symbol layer: " + QString::number( symlayer ) );
3305 delete it.value(); // we must delete it
3306 continue;
3307 }
3308
3309 // set subsymbol takes ownership
3310 const bool res = sym->symbolLayer( symlayer )->setSubSymbol( it.value() );
3311 if ( !res )
3312 {
3313 QgsDebugError( "symbol layer refused subsymbol: " + it.key() );
3314 }
3315
3316
3317 }
3318
3319 // now safely remove sub-symbol entries (they have been already deleted or the ownership was taken away)
3320 for ( int i = 0; i < subsymbols.count(); i++ )
3321 symbols.take( subsymbols[i] );
3322
3323 return symbols;
3324}
3325
3326QDomElement QgsSymbolLayerUtils::saveSymbols( QgsSymbolMap &symbols, const QString &tagName, QDomDocument &doc, const QgsReadWriteContext &context )
3327{
3328 QDomElement symbolsElem = doc.createElement( tagName );
3329
3330 // save symbols
3331 for ( QMap<QString, QgsSymbol *>::iterator its = symbols.begin(); its != symbols.end(); ++its )
3332 {
3333 const QDomElement symEl = saveSymbol( its.key(), its.value(), doc, context );
3334 symbolsElem.appendChild( symEl );
3335 }
3336
3337 return symbolsElem;
3338}
3339
3341{
3342 qDeleteAll( symbols );
3343 symbols.clear();
3344}
3345
3347{
3348 if ( !symbol )
3349 return nullptr;
3350
3351 std::unique_ptr< QMimeData >mimeData( new QMimeData );
3352
3353 QDomDocument symbolDoc;
3354 const QDomElement symbolElem = saveSymbol( QStringLiteral( "symbol" ), symbol, symbolDoc, QgsReadWriteContext() );
3355 symbolDoc.appendChild( symbolElem );
3356 mimeData->setText( symbolDoc.toString() );
3357
3358 mimeData->setImageData( symbolPreviewPixmap( symbol, QSize( 100, 100 ), 18 ).toImage() );
3359 mimeData->setColorData( symbol->color() );
3360
3361 return mimeData.release();
3362}
3363
3365{
3366 if ( !data )
3367 return nullptr;
3368
3369 const QString text = data->text();
3370 if ( !text.isEmpty() )
3371 {
3372 QDomDocument doc;
3373 QDomElement elem;
3374
3375 if ( doc.setContent( text ) )
3376 {
3377 elem = doc.documentElement();
3378
3379 if ( elem.nodeName() != QLatin1String( "symbol" ) )
3380 elem = elem.firstChildElement( QStringLiteral( "symbol" ) );
3381
3382 return loadSymbol( elem, QgsReadWriteContext() );
3383 }
3384 }
3385 return nullptr;
3386}
3387
3388
3390{
3391 const QString rampType = element.attribute( QStringLiteral( "type" ) );
3392
3393 // parse properties
3394 const QVariantMap props = QgsSymbolLayerUtils::parseProperties( element );
3395
3396 if ( rampType == QgsGradientColorRamp::typeString() )
3397 return QgsGradientColorRamp::create( props );
3398 else if ( rampType == QgsLimitedRandomColorRamp::typeString() )
3399 return QgsLimitedRandomColorRamp::create( props );
3400 else if ( rampType == QgsColorBrewerColorRamp::typeString() )
3401 return QgsColorBrewerColorRamp::create( props );
3402 else if ( rampType == QgsCptCityColorRamp::typeString() )
3403 return QgsCptCityColorRamp::create( props );
3404 else if ( rampType == QgsPresetSchemeColorRamp::typeString() )
3405 return QgsPresetSchemeColorRamp::create( props );
3406 else
3407 {
3408 QgsDebugError( "unknown colorramp type " + rampType );
3409 return nullptr;
3410 }
3411}
3412
3413
3414QDomElement QgsSymbolLayerUtils::saveColorRamp( const QString &name, QgsColorRamp *ramp, QDomDocument &doc )
3415{
3416 QDomElement rampEl = doc.createElement( QStringLiteral( "colorramp" ) );
3417 rampEl.setAttribute( QStringLiteral( "type" ), ramp->type() );
3418 rampEl.setAttribute( QStringLiteral( "name" ), name );
3419
3420 QgsSymbolLayerUtils::saveProperties( ramp->properties(), doc, rampEl );
3421 return rampEl;
3422}
3423
3424QVariant QgsSymbolLayerUtils::colorRampToVariant( const QString &name, QgsColorRamp *ramp )
3425{
3426 QVariantMap rampMap;
3427
3428 rampMap.insert( QStringLiteral( "type" ), ramp->type() );
3429 rampMap.insert( QStringLiteral( "name" ), name );
3430
3431 const QVariantMap properties = ramp->properties();
3432
3433 QVariantMap propertyMap;
3434 for ( auto property = properties.constBegin(); property != properties.constEnd(); ++property )
3435 {
3436 propertyMap.insert( property.key(), property.value() );
3437 }
3438
3439 rampMap.insert( QStringLiteral( "properties" ), propertyMap );
3440 return rampMap;
3441}
3442
3444{
3445 const QVariantMap rampMap = value.toMap();
3446
3447 const QString rampType = rampMap.value( QStringLiteral( "type" ) ).toString();
3448
3449 // parse properties
3450 const QVariantMap propertyMap = rampMap.value( QStringLiteral( "properties" ) ).toMap();
3451 QVariantMap props;
3452
3453 for ( auto property = propertyMap.constBegin(); property != propertyMap.constEnd(); ++property )
3454 {
3455 props.insert( property.key(), property.value().toString() );
3456 }
3457
3458 if ( rampType == QgsGradientColorRamp::typeString() )
3459 return QgsGradientColorRamp::create( props );
3460 else if ( rampType == QgsLimitedRandomColorRamp::typeString() )
3461 return QgsLimitedRandomColorRamp::create( props );
3462 else if ( rampType == QgsColorBrewerColorRamp::typeString() )
3463 return QgsColorBrewerColorRamp::create( props );
3464 else if ( rampType == QgsCptCityColorRamp::typeString() )
3465 return QgsCptCityColorRamp::create( props );
3466 else if ( rampType == QgsPresetSchemeColorRamp::typeString() )
3467 return QgsPresetSchemeColorRamp::create( props );
3468 else
3469 {
3470 QgsDebugError( "unknown colorramp type " + rampType );
3471 return nullptr;
3472 }
3473}
3474
3475QString QgsSymbolLayerUtils::colorToName( const QColor &color )
3476{
3477 if ( !color.isValid() )
3478 {
3479 return QString();
3480 }
3481
3482 //TODO - utilize a color names database (such as X11) to return nicer names
3483 //for now, just return hex codes
3484 return color.name();
3485}
3486
3487QList<QColor> QgsSymbolLayerUtils::parseColorList( const QString &colorStr )
3488{
3489 QList<QColor> colors;
3490
3491 //try splitting string at commas, spaces or newlines
3492 const thread_local QRegularExpression sepCommaSpaceRegExp( "(,|\\s)" );
3493 QStringList components = colorStr.simplified().split( sepCommaSpaceRegExp );
3494 QStringList::iterator it = components.begin();
3495 for ( ; it != components.end(); ++it )
3496 {
3497 const QColor result = parseColor( *it, true );
3498 if ( result.isValid() )
3499 {
3500 colors << result;
3501 }
3502 }
3503 if ( colors.length() > 0 )
3504 {
3505 return colors;
3506 }
3507
3508 //try splitting string at commas or newlines
3509 const thread_local QRegularExpression sepCommaRegExp( "(,|\n)" );
3510 components = colorStr.split( sepCommaRegExp );
3511 it = components.begin();
3512 for ( ; it != components.end(); ++it )
3513 {
3514 const QColor result = parseColor( *it, true );
3515 if ( result.isValid() )
3516 {
3517 colors << result;
3518 }
3519 }
3520 if ( colors.length() > 0 )
3521 {
3522 return colors;
3523 }
3524
3525 //try splitting string at whitespace or newlines
3526 components = colorStr.simplified().split( QString( ' ' ) );
3527 it = components.begin();
3528 for ( ; it != components.end(); ++it )
3529 {
3530 const QColor result = parseColor( *it, true );
3531 if ( result.isValid() )
3532 {
3533 colors << result;
3534 }
3535 }
3536 if ( colors.length() > 0 )
3537 {
3538 return colors;
3539 }
3540
3541 //try splitting string just at newlines
3542 components = colorStr.split( '\n' );
3543 it = components.begin();
3544 for ( ; it != components.end(); ++it )
3545 {
3546 const QColor result = parseColor( *it, true );
3547 if ( result.isValid() )
3548 {
3549 colors << result;
3550 }
3551 }
3552
3553 return colors;
3554}
3555
3556QMimeData *QgsSymbolLayerUtils::colorToMimeData( const QColor &color )
3557{
3558 //set both the mime color data (which includes alpha channel), and the text (which is the color's hex
3559 //value, and can be used when pasting colors outside of QGIS).
3560 QMimeData *mimeData = new QMimeData;
3561 mimeData->setColorData( QVariant( color ) );
3562 mimeData->setText( color.name() );
3563 return mimeData;
3564}
3565
3566QColor QgsSymbolLayerUtils::colorFromMimeData( const QMimeData *mimeData, bool &hasAlpha )
3567{
3568 //attempt to read color data directly from mime
3569 if ( mimeData->hasColor() )
3570 {
3571 QColor mimeColor = mimeData->colorData().value<QColor>();
3572 if ( mimeColor.isValid() )
3573 {
3574 hasAlpha = true;
3575 return mimeColor;
3576 }
3577 }
3578
3579 //attempt to intrepret a color from mime text data
3580 if ( mimeData->hasText() )
3581 {
3582 hasAlpha = false;
3583 QColor textColor = QgsSymbolLayerUtils::parseColorWithAlpha( mimeData->text(), hasAlpha );
3584 if ( textColor.isValid() )
3585 {
3586 return textColor;
3587 }
3588 }
3589
3590 //could not get color from mime data
3591 return QColor();
3592}
3593
3595{
3596 QgsNamedColorList mimeColors;
3597
3598 //prefer xml format
3599 if ( data->hasFormat( QStringLiteral( "text/xml" ) ) )
3600 {
3601 //get XML doc
3602 const QByteArray encodedData = data->data( QStringLiteral( "text/xml" ) );
3603 QDomDocument xmlDoc;
3604 xmlDoc.setContent( encodedData );
3605
3606 const QDomElement dragDataElem = xmlDoc.documentElement();
3607 if ( dragDataElem.tagName() == QLatin1String( "ColorSchemeModelDragData" ) )
3608 {
3609 const QDomNodeList nodeList = dragDataElem.childNodes();
3610 const int nChildNodes = nodeList.size();
3611 QDomElement currentElem;
3612
3613 for ( int i = 0; i < nChildNodes; ++i )
3614 {
3615 currentElem = nodeList.at( i ).toElement();
3616 if ( currentElem.isNull() )
3617 {
3618 continue;
3619 }
3620
3621 QPair< QColor, QString> namedColor;
3622 namedColor.first = QgsColorUtils::colorFromString( currentElem.attribute( QStringLiteral( "color" ), QStringLiteral( "255,255,255,255" ) ) );
3623 namedColor.second = currentElem.attribute( QStringLiteral( "label" ), QString() );
3624
3625 mimeColors << namedColor;
3626 }
3627 }
3628 }
3629
3630 if ( mimeColors.length() == 0 && data->hasFormat( QStringLiteral( "application/x-colorobject-list" ) ) )
3631 {
3632 //get XML doc
3633 const QByteArray encodedData = data->data( QStringLiteral( "application/x-colorobject-list" ) );
3634 QDomDocument xmlDoc;
3635 xmlDoc.setContent( encodedData );
3636
3637 const QDomNodeList colorsNodes = xmlDoc.elementsByTagName( QStringLiteral( "colors" ) );
3638 if ( colorsNodes.length() > 0 )
3639 {
3640 const QDomElement colorsElem = colorsNodes.at( 0 ).toElement();
3641 const QDomNodeList colorNodeList = colorsElem.childNodes();
3642 const int nChildNodes = colorNodeList.size();
3643 QDomElement currentElem;
3644
3645 for ( int i = 0; i < nChildNodes; ++i )
3646 {
3647 //li element
3648 currentElem = colorNodeList.at( i ).toElement();
3649 if ( currentElem.isNull() )
3650 {
3651 continue;
3652 }
3653
3654 const QDomNodeList colorNodes = currentElem.elementsByTagName( QStringLiteral( "color" ) );
3655 const QDomNodeList nameNodes = currentElem.elementsByTagName( QStringLiteral( "name" ) );
3656
3657 if ( colorNodes.length() > 0 )
3658 {
3659 const QDomElement colorElem = colorNodes.at( 0 ).toElement();
3660
3661 const QStringList colorParts = colorElem.text().simplified().split( ' ' );
3662 if ( colorParts.length() < 3 )
3663 {
3664 continue;
3665 }
3666
3667 const int red = colorParts.at( 0 ).toDouble() * 255;
3668 const int green = colorParts.at( 1 ).toDouble() * 255;
3669 const int blue = colorParts.at( 2 ).toDouble() * 255;
3670 QPair< QColor, QString> namedColor;
3671 namedColor.first = QColor( red, green, blue );
3672 if ( nameNodes.length() > 0 )
3673 {
3674 const QDomElement nameElem = nameNodes.at( 0 ).toElement();
3675 namedColor.second = nameElem.text();
3676 }
3677 mimeColors << namedColor;
3678 }
3679 }
3680 }
3681 }
3682
3683 if ( mimeColors.length() == 0 && data->hasText() )
3684 {
3685 //attempt to read color data from mime text
3686 QList< QColor > parsedColors = QgsSymbolLayerUtils::parseColorList( data->text() );
3687 QList< QColor >::iterator it = parsedColors.begin();
3688 for ( ; it != parsedColors.end(); ++it )
3689 {
3690 mimeColors << qMakePair( *it, QString() );
3691 }
3692 }
3693
3694 if ( mimeColors.length() == 0 && data->hasColor() )
3695 {
3696 //attempt to read color data directly from mime
3697 const QColor mimeColor = data->colorData().value<QColor>();
3698 if ( mimeColor.isValid() )
3699 {
3700 mimeColors << qMakePair( mimeColor, QString() );
3701 }
3702 }
3703
3704 return mimeColors;
3705}
3706
3707QMimeData *QgsSymbolLayerUtils::colorListToMimeData( const QgsNamedColorList &colorList, const bool allFormats )
3708{
3709 //native format
3710 QMimeData *mimeData = new QMimeData();
3711 QDomDocument xmlDoc;
3712 QDomElement xmlRootElement = xmlDoc.createElement( QStringLiteral( "ColorSchemeModelDragData" ) );
3713 xmlDoc.appendChild( xmlRootElement );
3714
3715 QgsNamedColorList::const_iterator colorIt = colorList.constBegin();
3716 for ( ; colorIt != colorList.constEnd(); ++colorIt )
3717 {
3718 QDomElement namedColor = xmlDoc.createElement( QStringLiteral( "NamedColor" ) );
3719 namedColor.setAttribute( QStringLiteral( "color" ), QgsColorUtils::colorToString( ( *colorIt ).first ) );
3720 namedColor.setAttribute( QStringLiteral( "label" ), ( *colorIt ).second );
3721 xmlRootElement.appendChild( namedColor );
3722 }
3723 mimeData->setData( QStringLiteral( "text/xml" ), xmlDoc.toByteArray() );
3724
3725 if ( !allFormats )
3726 {
3727 return mimeData;
3728 }
3729
3730 //set mime text to list of hex values
3731 colorIt = colorList.constBegin();
3732 QStringList colorListString;
3733 for ( ; colorIt != colorList.constEnd(); ++colorIt )
3734 {
3735 colorListString << ( *colorIt ).first.name();
3736 }
3737 mimeData->setText( colorListString.join( QLatin1Char( '\n' ) ) );
3738
3739 //set mime color data to first color
3740 if ( colorList.length() > 0 )
3741 {
3742 mimeData->setColorData( QVariant( colorList.at( 0 ).first ) );
3743 }
3744
3745 return mimeData;
3746}
3747
3748bool QgsSymbolLayerUtils::saveColorsToGpl( QFile &file, const QString &paletteName, const QgsNamedColorList &colors )
3749{
3750 if ( !file.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3751 {
3752 return false;
3753 }
3754
3755 QTextStream stream( &file );
3756 stream << "GIMP Palette" << Qt::endl;
3757 if ( paletteName.isEmpty() )
3758 {
3759 stream << "Name: QGIS Palette" << Qt::endl;
3760 }
3761 else
3762 {
3763 stream << "Name: " << paletteName << Qt::endl;
3764 }
3765 stream << "Columns: 4" << Qt::endl;
3766 stream << '#' << Qt::endl;
3767
3768 for ( QgsNamedColorList::ConstIterator colorIt = colors.constBegin(); colorIt != colors.constEnd(); ++colorIt )
3769 {
3770 const QColor color = ( *colorIt ).first;
3771 if ( !color.isValid() )
3772 {
3773 continue;
3774 }
3775 stream << QStringLiteral( "%1 %2 %3" ).arg( color.red(), 3 ).arg( color.green(), 3 ).arg( color.blue(), 3 );
3776 stream << "\t" << ( ( *colorIt ).second.isEmpty() ? color.name() : ( *colorIt ).second ) << Qt::endl;
3777 }
3778 file.close();
3779
3780 return true;
3781}
3782
3784{
3785 QgsNamedColorList importedColors;
3786
3787 if ( !file.open( QIODevice::ReadOnly ) )
3788 {
3789 ok = false;
3790 return importedColors;
3791 }
3792
3793 QTextStream in( &file );
3794
3795 QString line = in.readLine();
3796 if ( !line.startsWith( QLatin1String( "GIMP Palette" ) ) )
3797 {
3798 ok = false;
3799 return importedColors;
3800 }
3801
3802 //find name line
3803 while ( !in.atEnd() && !line.startsWith( QLatin1String( "Name:" ) ) && !line.startsWith( '#' ) )
3804 {
3805 line = in.readLine();
3806 }
3807 if ( line.startsWith( QLatin1String( "Name:" ) ) )
3808 {
3809 const thread_local QRegularExpression nameRx( "Name:\\s*(\\S.*)$" );
3810 const QRegularExpressionMatch match = nameRx.match( line );
3811 if ( match.hasMatch() )
3812 {
3813 name = match.captured( 1 );
3814 }
3815 }
3816
3817 //ignore lines until after "#"
3818 while ( !in.atEnd() && !line.startsWith( '#' ) )
3819 {
3820 line = in.readLine();
3821 }
3822 if ( in.atEnd() )
3823 {
3824 ok = false;
3825 return importedColors;
3826 }
3827
3828 //ready to start reading colors
3829 const thread_local QRegularExpression rx( "^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)(\\s.*)?$" );
3830 while ( !in.atEnd() )
3831 {
3832 line = in.readLine();
3833 const QRegularExpressionMatch match = rx.match( line );
3834 if ( !match.hasMatch() )
3835 {
3836 continue;
3837 }
3838 const int red = match.captured( 1 ).toInt();
3839 const int green = match.captured( 2 ).toInt();
3840 const int blue = match.captured( 3 ).toInt();
3841 const QColor color = QColor( red, green, blue );
3842 if ( !color.isValid() )
3843 {
3844 continue;
3845 }
3846
3847 //try to read color name
3848 QString label;
3849 if ( rx.captureCount() > 3 )
3850 {
3851 label = match.captured( 4 ).simplified();
3852 }
3853 else
3854 {
3855 label = colorToName( color );
3856 }
3857
3858 importedColors << qMakePair( color, label );
3859 }
3860
3861 file.close();
3862 ok = true;
3863 return importedColors;
3864}
3865
3866QColor QgsSymbolLayerUtils::parseColor( const QString &colorStr, bool strictEval )
3867{
3868 bool hasAlpha;
3869 return parseColorWithAlpha( colorStr, hasAlpha, strictEval );
3870}
3871
3872QColor QgsSymbolLayerUtils::parseColorWithAlpha( const QString &colorStr, bool &containsAlpha, bool strictEval )
3873{
3874 QColor parsedColor;
3875
3876 const thread_local QRegularExpression hexColorAlphaRx( "^\\s*#?([0-9a-fA-F]{6})([0-9a-fA-F]{2})\\s*$" );
3877 QRegularExpressionMatch match = hexColorAlphaRx.match( colorStr );
3878
3879 //color in hex format "#aabbcc", but not #aabbccdd
3880 if ( !match.hasMatch() && QColor::isValidColor( colorStr ) )
3881 {
3882 //string is a valid hex color string
3883 parsedColor.setNamedColor( colorStr );
3884 if ( parsedColor.isValid() )
3885 {
3886 containsAlpha = false;
3887 return parsedColor;
3888 }
3889 }
3890
3891 //color in hex format, with alpha
3892 if ( match.hasMatch() )
3893 {
3894 const QString hexColor = match.captured( 1 );
3895 parsedColor.setNamedColor( QStringLiteral( "#" ) + hexColor );
3896 bool alphaOk;
3897 const int alphaHex = match.captured( 2 ).toInt( &alphaOk, 16 );
3898
3899 if ( parsedColor.isValid() && alphaOk )
3900 {
3901 parsedColor.setAlpha( alphaHex );
3902 containsAlpha = true;
3903 return parsedColor;
3904 }
3905 }
3906
3907 if ( !strictEval )
3908 {
3909 //color in hex format, without #
3910 const thread_local QRegularExpression hexColorRx2( "^\\s*(?:[0-9a-fA-F]{3}){1,2}\\s*$" );
3911 if ( colorStr.indexOf( hexColorRx2 ) != -1 )
3912 {
3913 //add "#" and parse
3914 parsedColor.setNamedColor( QStringLiteral( "#" ) + colorStr );
3915 if ( parsedColor.isValid() )
3916 {
3917 containsAlpha = false;
3918 return parsedColor;
3919 }
3920 }
3921 }
3922
3923 //color in (rrr,ggg,bbb) format, brackets and rgb prefix optional
3924 const thread_local QRegularExpression rgbFormatRx( "^\\s*(?:rgb)?\\(?\\s*((?:[01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(?:\\.\\d*)?)\\s*,\\s*((?:[01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(?:\\.\\d*)?)\\s*,\\s*((?:[01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(?:\\.\\d*)?)\\s*\\)?\\s*;?\\s*$" );
3925 match = rgbFormatRx.match( colorStr );
3926 if ( match.hasMatch() )
3927 {
3928 bool rOk = false;
3929 bool gOk = false;
3930 bool bOk = false;
3931 const int r = match.captured( 1 ).toInt( &rOk );
3932 const int g = match.captured( 2 ).toInt( &gOk );
3933 const int b = match.captured( 3 ).toInt( &bOk );
3934
3935 if ( !rOk || !gOk || !bOk )
3936 {
3937 const float rFloat = match.captured( 1 ).toFloat();
3938 const float gFloat = match.captured( 2 ).toFloat();
3939 const float bFloat = match.captured( 3 ).toFloat();
3940 parsedColor.setRgbF( rFloat / 255.0, gFloat / 255.0, bFloat / 255.0 );
3941 }
3942 else
3943 {
3944 parsedColor.setRgb( r, g, b );
3945 }
3946
3947 if ( parsedColor.isValid() )
3948 {
3949 containsAlpha = false;
3950 return parsedColor;
3951 }
3952 }
3953
3954 //color in hsl(h,s,l) format, brackets optional
3955 const thread_local QRegularExpression hslFormatRx( "^\\s*hsl\\(?\\s*(\\d+(?:\\.\\d*)?)\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*\\)?\\s*;?\\s*$" );
3956 match = hslFormatRx.match( colorStr );
3957 if ( match.hasMatch() )
3958 {
3959 bool hOk = false;
3960 bool sOk = false;
3961 bool lOk = false;
3962 const int h = match.captured( 1 ).toInt( &hOk );
3963 const int s = match.captured( 2 ).toInt( &sOk );
3964 const int l = match.captured( 3 ).toInt( &lOk );
3965
3966 if ( !hOk || !sOk || !lOk )
3967 {
3968 const float hFloat = match.captured( 1 ).toFloat();
3969 const float sFloat = match.captured( 2 ).toFloat();
3970 const float lFloat = match.captured( 3 ).toFloat();
3971 parsedColor.setHslF( hFloat / 360.0, sFloat / 100.0, lFloat / 100.0 );
3972 }
3973 else
3974 {
3975 parsedColor.setHsl( h, s / 100.0 * 255.0, l / 100.0 * 255.0 );
3976 }
3977 if ( parsedColor.isValid() )
3978 {
3979 containsAlpha = false;
3980 return parsedColor;
3981 }
3982 }
3983
3984 //color in (r%,g%,b%) format, brackets and rgb prefix optional
3985 const thread_local QRegularExpression rgbPercentFormatRx( "^\\s*(?:rgb)?\\(?\\s*(100|0*\\d{1,2}(?:\\.\\d*)?)\\s*%\\s*,\\s*(100|0*\\d{1,2}(?:\\.\\d*)?)\\s*%\\s*,\\s*(100|0*\\d{1,2}(?:\\.\\d*)?)\\s*%\\s*\\)?\\s*;?\\s*$" );
3986 match = rgbPercentFormatRx.match( colorStr );
3987 if ( match.hasMatch() )
3988 {
3989 const double r = match.captured( 1 ).toDouble() / 100;
3990 const double g = match.captured( 2 ).toDouble() / 100;
3991 const double b = match.captured( 3 ).toDouble() / 100;
3992 parsedColor.setRgbF( r, g, b );
3993 if ( parsedColor.isValid() )
3994 {
3995 containsAlpha = false;
3996 return parsedColor;
3997 }
3998 }
3999
4000 //color in (r,g,b,a) format, brackets and rgba prefix optional
4001 const thread_local QRegularExpression rgbaFormatRx( "^\\s*(?:rgba)?\\(?\\s*((?:[01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(?:\\.\\d*)?)\\s*,\\s*((?:[01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(?:\\.\\d*)?)\\s*,\\s*((?:[01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(?:\\.\\d*)?)\\s*,\\s*(0|0?\\.\\d*|1(?:\\.0*)?)\\s*\\)?\\s*;?\\s*$" );
4002 match = rgbaFormatRx.match( colorStr );
4003 if ( match.hasMatch() )
4004 {
4005 bool rOk = false;
4006 bool gOk = false;
4007 bool bOk = false;
4008 const int r = match.captured( 1 ).toInt( &rOk );
4009 const int g = match.captured( 2 ).toInt( &gOk );
4010 const int b = match.captured( 3 ).toInt( &bOk );
4011 const double aDouble = match.captured( 4 ).toDouble();
4012
4013 if ( !rOk || !gOk || !bOk )
4014 {
4015 const float rFloat = match.captured( 1 ).toFloat();
4016 const float gFloat = match.captured( 2 ).toFloat();
4017 const float bFloat = match.captured( 3 ).toFloat();
4018 parsedColor.setRgbF( rFloat / 255.0, gFloat / 255.0, bFloat / 255.0, aDouble );
4019 }
4020 else
4021 {
4022 const int a = static_cast< int >( std::round( match.captured( 4 ).toDouble() * 255.0 ) );
4023 parsedColor.setRgb( r, g, b, a );
4024 }
4025 if ( parsedColor.isValid() )
4026 {
4027 containsAlpha = true;
4028 return parsedColor;
4029 }
4030 }
4031
4032 //color in (r%,g%,b%,a) format, brackets and rgba prefix optional
4033 const thread_local QRegularExpression rgbaPercentFormatRx( "^\\s*(?:rgba)?\\(?\\s*(100|0*\\d{1,2}(?:\\.\\d*)?)\\s*%\\s*,\\s*(100|0*\\d{1,2}(?:\\.\\d*)?)\\s*%\\s*,\\s*(100|0*\\d{1,2}(?:\\.\\d*)?)\\s*%\\s*,\\s*(0|0?\\.\\d*|1(?:\\.0*)?)\\s*\\)?\\s*;?\\s*$" );
4034 match = rgbaPercentFormatRx.match( colorStr );
4035 if ( match.hasMatch() )
4036 {
4037 const double r = match.captured( 1 ).toDouble() / 100;
4038 const double g = match.captured( 2 ).toDouble() / 100;
4039 const double b = match.captured( 3 ).toDouble() / 100;
4040 const double a = match.captured( 4 ).toDouble();
4041 parsedColor.setRgbF( r, g, b, a );
4042 if ( parsedColor.isValid() )
4043 {
4044 containsAlpha = true;
4045 return parsedColor;
4046 }
4047 }
4048
4049 //color in hsla(h,s%,l%,a) format, brackets optional
4050 const thread_local QRegularExpression hslaPercentFormatRx( "^\\s*hsla\\(?\\s*(\\d+(?:\\.\\d*)?)\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*,\\s*([\\d\\.]+)\\s*\\)?\\s*;?\\s*$" );
4051 match = hslaPercentFormatRx.match( colorStr );
4052 if ( match.hasMatch() )
4053 {
4054 bool hOk = false;
4055 bool sOk = false;
4056 bool lOk = false;
4057 const int h = match.captured( 1 ).toInt( &hOk );
4058 const int s = match.captured( 2 ).toInt( &sOk );
4059 const int l = match.captured( 3 ).toInt( &lOk );
4060 const double aDouble = match.captured( 4 ).toDouble();
4061
4062 if ( !hOk || !sOk || !lOk )
4063 {
4064 const float hFloat = match.captured( 1 ).toFloat();
4065 const float sFloat = match.captured( 2 ).toFloat();
4066 const float lFloat = match.captured( 3 ).toFloat();
4067 parsedColor.setHslF( hFloat / 360.0, sFloat / 100.0, lFloat / 100.0, aDouble );
4068 }
4069 else
4070 {
4071 const int a = std::round( aDouble * 255.0 );
4072 parsedColor.setHsl( h, s / 100.0 * 255.0, l / 100.0 * 255.0, a );
4073 }
4074
4075 if ( parsedColor.isValid() )
4076 {
4077 containsAlpha = true;
4078 return parsedColor;
4079 }
4080 }
4081
4082 //couldn't parse string as color
4083 return QColor();
4084}
4085
4086void QgsSymbolLayerUtils::multiplyImageOpacity( QImage *image, qreal opacity )
4087{
4088 if ( !image )
4089 {
4090 return;
4091 }
4092
4093 QRgb myRgb;
4094 const QImage::Format format = image->format();
4095 if ( format != QImage::Format_ARGB32_Premultiplied && format != QImage::Format_ARGB32 )
4096 {
4097 QgsDebugError( QStringLiteral( "no alpha channel." ) );
4098 return;
4099 }
4100
4101 //change the alpha component of every pixel
4102 for ( int heightIndex = 0; heightIndex < image->height(); ++heightIndex )
4103 {
4104 QRgb *scanLine = reinterpret_cast< QRgb * >( image->scanLine( heightIndex ) );
4105 for ( int widthIndex = 0; widthIndex < image->width(); ++widthIndex )
4106 {
4107 myRgb = scanLine[widthIndex];
4108 if ( format == QImage::Format_ARGB32_Premultiplied )
4109 scanLine[widthIndex] = qRgba( opacity * qRed( myRgb ), opacity * qGreen( myRgb ), opacity * qBlue( myRgb ), opacity * qAlpha( myRgb ) );
4110 else
4111 scanLine[widthIndex] = qRgba( qRed( myRgb ), qGreen( myRgb ), qBlue( myRgb ), opacity * qAlpha( myRgb ) );
4112 }
4113 }
4114}
4115
4116void QgsSymbolLayerUtils::blurImageInPlace( QImage &image, QRect rect, int radius, bool alphaOnly )
4117{
4118 // culled from Qt's qpixmapfilter.cpp, see: http://www.qtcentre.org/archive/index.php/t-26534.html
4119 const int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
4120 const int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius - 1];
4121
4122 if ( image.format() != QImage::Format_ARGB32_Premultiplied
4123 && image.format() != QImage::Format_RGB32 )
4124 {
4125 image = image.convertToFormat( QImage::Format_ARGB32_Premultiplied );
4126 }
4127
4128 const int r1 = rect.top();
4129 const int r2 = rect.bottom();
4130 const int c1 = rect.left();
4131 const int c2 = rect.right();
4132
4133 const int bpl = image.bytesPerLine();
4134 int rgba[4];
4135 unsigned char *p;
4136
4137 int i1 = 0;
4138 int i2 = 3;
4139
4140 if ( alphaOnly ) // this seems to only work right for a black color
4141 i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
4142
4143 for ( int col = c1; col <= c2; col++ )
4144 {
4145 p = image.scanLine( r1 ) + col * 4;
4146 for ( int i = i1; i <= i2; i++ )
4147 rgba[i] = p[i] << 4;
4148
4149 p += bpl;
4150 for ( int j = r1; j < r2; j++, p += bpl )
4151 for ( int i = i1; i <= i2; i++ )
4152 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4153 }
4154
4155 for ( int row = r1; row <= r2; row++ )
4156 {
4157 p = image.scanLine( row ) + c1 * 4;
4158 for ( int i = i1; i <= i2; i++ )
4159 rgba[i] = p[i] << 4;
4160
4161 p += 4;
4162 for ( int j = c1; j < c2; j++, p += 4 )
4163 for ( int i = i1; i <= i2; i++ )
4164 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4165 }
4166
4167 for ( int col = c1; col <= c2; col++ )
4168 {
4169 p = image.scanLine( r2 ) + col * 4;
4170 for ( int i = i1; i <= i2; i++ )
4171 rgba[i] = p[i] << 4;
4172
4173 p -= bpl;
4174 for ( int j = r1; j < r2; j++, p -= bpl )
4175 for ( int i = i1; i <= i2; i++ )
4176 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4177 }
4178
4179 for ( int row = r1; row <= r2; row++ )
4180 {
4181 p = image.scanLine( row ) + c2 * 4;
4182 for ( int i = i1; i <= i2; i++ )
4183 rgba[i] = p[i] << 4;
4184
4185 p -= 4;
4186 for ( int j = c1; j < c2; j++, p -= 4 )
4187 for ( int i = i1; i <= i2; i++ )
4188 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4189 }
4190}
4191
4192void QgsSymbolLayerUtils::premultiplyColor( QColor &rgb, int alpha )
4193{
4194 if ( alpha != 255 && alpha > 0 )
4195 {
4196 // Semi-transparent pixel. We need to adjust the colors for ARGB32_Premultiplied images
4197 // where color values have to be premultiplied by alpha
4198 const double alphaFactor = alpha / 255.;
4199 int r = 0, g = 0, b = 0;
4200 rgb.getRgb( &r, &g, &b );
4201
4202 r *= alphaFactor;
4203 g *= alphaFactor;
4204 b *= alphaFactor;
4205 rgb.setRgb( r, g, b, alpha );
4206 }
4207 else if ( alpha == 0 )
4208 {
4209 rgb.setRgb( 0, 0, 0, 0 );
4210 }
4211}
4212
4214{
4215 QgsSimpleFillSymbolLayer *simpleFill = dynamic_cast< QgsSimpleFillSymbolLayer *>( fill );
4216 QgsSimpleLineSymbolLayer *simpleLine = dynamic_cast< QgsSimpleLineSymbolLayer *>( outline );
4217
4218 if ( !simpleFill || !simpleLine )
4219 return false;
4220
4221 if ( simpleLine->useCustomDashPattern() )
4222 return false;
4223
4224 if ( simpleLine->dashPatternOffset() )
4225 return false;
4226
4227 if ( simpleLine->alignDashPattern() )
4228 return false;
4229
4230 if ( simpleLine->tweakDashPatternOnCorners() )
4231 return false;
4232
4233 if ( simpleLine->trimDistanceStart() || simpleLine->trimDistanceEnd() )
4234 return false;
4235
4236 if ( simpleLine->drawInsidePolygon() )
4237 return false;
4238
4239 if ( simpleLine->ringFilter() != QgsSimpleLineSymbolLayer::AllRings )
4240 return false;
4241
4242 if ( simpleLine->offset() )
4243 return false;
4244
4245 if ( simpleLine->hasDataDefinedProperties() )
4246 return false;
4247
4248 // looks good!
4249 simpleFill->setStrokeColor( simpleLine->color() );
4250 simpleFill->setStrokeWidth( simpleLine->width() );
4251 simpleFill->setStrokeWidthUnit( simpleLine->widthUnit() );
4252 simpleFill->setStrokeWidthMapUnitScale( simpleLine->widthMapUnitScale() );
4253 simpleFill->setStrokeStyle( simpleLine->penStyle() );
4254 simpleFill->setPenJoinStyle( simpleLine->penJoinStyle() );
4255 return true;
4256}
4257
4258void QgsSymbolLayerUtils::sortVariantList( QList<QVariant> &list, Qt::SortOrder order )
4259{
4260 if ( order == Qt::AscendingOrder )
4261 {
4262 //std::sort( list.begin(), list.end(), _QVariantLessThan );
4263 std::sort( list.begin(), list.end(), qgsVariantLessThan );
4264 }
4265 else // Qt::DescendingOrder
4266 {
4267 //std::sort( list.begin(), list.end(), _QVariantGreaterThan );
4268 std::sort( list.begin(), list.end(), qgsVariantGreaterThan );
4269 }
4270}
4271
4272QPointF QgsSymbolLayerUtils::pointOnLineWithDistance( QPointF startPoint, QPointF directionPoint, double distance )
4273{
4274 const double dx = directionPoint.x() - startPoint.x();
4275 const double dy = directionPoint.y() - startPoint.y();
4276 const double length = std::sqrt( dx * dx + dy * dy );
4277 const double scaleFactor = distance / length;
4278 return QPointF( startPoint.x() + dx * scaleFactor, startPoint.y() + dy * scaleFactor );
4279}
4280
4281
4283{
4284 // copied from QgsMarkerCatalogue - TODO: unify //#spellok
4285 QStringList list;
4286 QStringList svgPaths = QgsApplication::svgPaths();
4287
4288 for ( int i = 0; i < svgPaths.size(); i++ )
4289 {
4290 const QDir dir( svgPaths[i] );
4291 const auto svgSubPaths = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
4292 for ( const QString &item : svgSubPaths )
4293 {
4294 svgPaths.insert( i + 1, dir.path() + '/' + item );
4295 }
4296
4297 const auto svgFiles = dir.entryList( QStringList( "*.svg" ), QDir::Files );
4298 for ( const QString &item : svgFiles )
4299 {
4300 // TODO test if it is correct SVG
4301 list.append( dir.path() + '/' + item );
4302 }
4303 }
4304 return list;
4305}
4306
4307// Stripped down version of listSvgFiles() for specified directory
4308QStringList QgsSymbolLayerUtils::listSvgFilesAt( const QString &directory )
4309{
4310 // TODO anything that applies for the listSvgFiles() applies this also
4311
4312 QStringList list;
4313 QStringList svgPaths;
4314 svgPaths.append( directory );
4315
4316 for ( int i = 0; i < svgPaths.size(); i++ )
4317 {
4318 const QDir dir( svgPaths[i] );
4319 const auto svgSubPaths = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
4320 for ( const QString &item : svgSubPaths )
4321 {
4322 svgPaths.insert( i + 1, dir.path() + '/' + item );
4323 }
4324
4325 const auto svgFiles = dir.entryList( QStringList( "*.svg" ), QDir::Files );
4326 for ( const QString &item : svgFiles )
4327 {
4328 list.append( dir.path() + '/' + item );
4329 }
4330 }
4331 return list;
4332
4333}
4334
4335QString QgsSymbolLayerUtils::svgSymbolNameToPath( const QString &n, const QgsPathResolver &pathResolver )
4336{
4337 if ( n.isEmpty() )
4338 return QString();
4339
4340 if ( n.startsWith( QLatin1String( "base64:" ) ) )
4341 return n;
4342
4343 // we might have a full path...
4344 if ( QFileInfo::exists( n ) )
4345 return QFileInfo( n ).canonicalFilePath();
4346
4347 QString name = n;
4348 // or it might be an url...
4349 if ( name.contains( QLatin1String( "://" ) ) )
4350 {
4351 const QUrl url( name );
4352 if ( url.isValid() && !url.scheme().isEmpty() )
4353 {
4354 if ( url.scheme().compare( QLatin1String( "file" ), Qt::CaseInsensitive ) == 0 )
4355 {
4356 // it's a url to a local file
4357 name = url.toLocalFile();
4358 if ( QFile( name ).exists() )
4359 {
4360 return QFileInfo( name ).canonicalFilePath();
4361 }
4362 }
4363 else
4364 {
4365 // it's a url pointing to a online resource
4366 return name;
4367 }
4368 }
4369 }
4370
4371 // SVG symbol not found - probably a relative path was used
4372
4373 QStringList svgPaths = QgsApplication::svgPaths();
4374 for ( int i = 0; i < svgPaths.size(); i++ )
4375 {
4376 QString svgPath = svgPaths[i];
4377 if ( svgPath.endsWith( QChar( '/' ) ) )
4378 {
4379 svgPath.chop( 1 );
4380 }
4381
4382 QgsDebugMsgLevel( "SvgPath: " + svgPath, 3 );
4383 // Not sure why to lowest dir was used instead of full relative path, it was causing #8664
4384 //QFileInfo myInfo( name );
4385 //QString myFileName = myInfo.fileName(); // foo.svg
4386 //QString myLowestDir = myInfo.dir().dirName();
4387 //QString myLocalPath = svgPath + QString( myLowestDir.isEmpty() ? "" : '/' + myLowestDir ) + '/' + myFileName;
4388 const QString myLocalPath = svgPath + QDir::separator() + name;
4389
4390 QgsDebugMsgLevel( "Alternative svg path: " + myLocalPath, 3 );
4391 if ( QFile( myLocalPath ).exists() )
4392 {
4393 QgsDebugMsgLevel( QStringLiteral( "Svg found in alternative path" ), 3 );
4394 return QFileInfo( myLocalPath ).canonicalFilePath();
4395 }
4396 }
4397
4398 return pathResolver.readPath( name );
4399}
4400
4401QString QgsSymbolLayerUtils::svgSymbolPathToName( const QString &p, const QgsPathResolver &pathResolver )
4402{
4403 if ( p.isEmpty() )
4404 return QString();
4405
4406 if ( p.startsWith( QLatin1String( "base64:" ) ) )
4407 return p;
4408
4409 if ( !QFileInfo::exists( p ) )
4410 return p;
4411
4412 QString path = QFileInfo( p ).canonicalFilePath();
4413
4414 QStringList svgPaths = QgsApplication::svgPaths();
4415
4416 bool isInSvgPaths = false;
4417 for ( int i = 0; i < svgPaths.size(); i++ )
4418 {
4419 const QString dir = QFileInfo( svgPaths[i] ).canonicalFilePath();
4420
4421 if ( !dir.isEmpty() && path.startsWith( dir ) )
4422 {
4423 path = path.mid( dir.size() + 1 );
4424 isInSvgPaths = true;
4425 break;
4426 }
4427 }
4428
4429 if ( isInSvgPaths )
4430 return path;
4431
4432 return pathResolver.writePath( path );
4433}
4434
4435
4436QPointF QgsSymbolLayerUtils::polygonCentroid( const QPolygonF &points )
4437{
4438 //Calculate the centroid of points
4439 double cx = 0, cy = 0;
4440 double area, sum = 0;
4441 for ( int i = points.count() - 1, j = 0; j < points.count(); i = j++ )
4442 {
4443 const QPointF &p1 = points[i];
4444 const QPointF &p2 = points[j];
4445 area = p1.x() * p2.y() - p1.y() * p2.x();
4446 sum += area;
4447 cx += ( p1.x() + p2.x() ) * area;
4448 cy += ( p1.y() + p2.y() ) * area;
4449 }
4450 sum *= 3.0;
4451 if ( qgsDoubleNear( sum, 0.0 ) )
4452 {
4453 // the linear ring is invalid - let's fall back to a solution that will still
4454 // allow us render at least something (instead of just returning point nan,nan)
4455 if ( points.count() >= 2 )
4456 return QPointF( ( points[0].x() + points[1].x() ) / 2, ( points[0].y() + points[1].y() ) / 2 );
4457 else if ( points.count() == 1 )
4458 return points[0];
4459 else
4460 return QPointF(); // hopefully we shouldn't ever get here
4461 }
4462 cx /= sum;
4463 cy /= sum;
4464
4465 return QPointF( cx, cy );
4466}
4467
4468QPointF QgsSymbolLayerUtils::polygonPointOnSurface( const QPolygonF &points, const QVector<QPolygonF> *rings )
4469{
4470 QPointF centroid = QgsSymbolLayerUtils::polygonCentroid( points );
4471
4472 if ( ( rings && rings->count() > 0 ) || !pointInPolygon( points, centroid ) )
4473 {
4474 unsigned int i, pointCount = points.count();
4475 QgsPolylineXY polyline( pointCount );
4476 for ( i = 0; i < pointCount; ++i ) polyline[i] = QgsPointXY( points[i].x(), points[i].y() );
4477 QgsGeometry geom = QgsGeometry::fromPolygonXY( QgsPolygonXY() << polyline );
4478 if ( !geom.isNull() )
4479 {
4480 if ( rings )
4481 {
4482 for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
4483 {
4484 pointCount = ( *ringIt ).count();
4485 QgsPolylineXY polyline( pointCount );
4486 for ( i = 0; i < pointCount; ++i ) polyline[i] = QgsPointXY( ( *ringIt )[i].x(), ( *ringIt )[i].y() );
4487 geom.addRing( polyline );
4488 }
4489 }
4490
4491 const QgsGeometry pointOnSurfaceGeom = geom.pointOnSurface();
4492 if ( !pointOnSurfaceGeom.isNull() )
4493 {
4494 const QgsPointXY point = pointOnSurfaceGeom.asPoint();
4495 centroid.setX( point.x() );
4496 centroid.setY( point.y() );
4497 }
4498 }
4499 }
4500
4501 return QPointF( centroid.x(), centroid.y() );
4502}
4503
4504bool QgsSymbolLayerUtils::pointInPolygon( const QPolygonF &points, QPointF point )
4505{
4506 bool inside = false;
4507
4508 const double x = point.x();
4509 const double y = point.y();
4510
4511 for ( int i = 0, j = points.count() - 1; i < points.count(); i++ )
4512 {
4513 const QPointF &p1 = points[i];
4514 const QPointF &p2 = points[j];
4515
4516 if ( qgsDoubleNear( p1.x(), x ) && qgsDoubleNear( p1.y(), y ) )
4517 return true;
4518
4519 if ( ( p1.y() < y && p2.y() >= y ) || ( p2.y() < y && p1.y() >= y ) )
4520 {
4521 if ( p1.x() + ( y - p1.y() ) / ( p2.y() - p1.y() ) * ( p2.x() - p1.x() ) <= x )
4522 inside = !inside;
4523 }
4524
4525 j = i;
4526 }
4527 return inside;
4528}
4529
4530double QgsSymbolLayerUtils::polylineLength( const QPolygonF &polyline )
4531{
4532 if ( polyline.size() < 2 )
4533 return 0;
4534
4535 double totalLength = 0;
4536 auto it = polyline.begin();
4537 QPointF p1 = *it++;
4538 for ( ; it != polyline.end(); ++it )
4539 {
4540 const QPointF p2 = *it;
4541 const double segmentLength = std::sqrt( std::pow( p1.x() - p2.x(), 2.0 ) + std::pow( p1.y() - p2.y(), 2.0 ) );
4542 totalLength += segmentLength;
4543 p1 = p2;
4544 }
4545 return totalLength;
4546}
4547
4548QPolygonF QgsSymbolLayerUtils::polylineSubstring( const QPolygonF &polyline, double startOffset, double endOffset )
4549{
4550 if ( polyline.size() < 2 )
4551 return QPolygonF();
4552
4553 double totalLength = 0;
4554 auto it = polyline.begin();
4555 QPointF p1 = *it++;
4556 std::vector< double > segmentLengths( polyline.size() - 1 );
4557 auto segmentLengthIt = segmentLengths.begin();
4558 for ( ; it != polyline.end(); ++it )
4559 {
4560 const QPointF p2 = *it;
4561 *segmentLengthIt = std::sqrt( std::pow( p1.x() - p2.x(), 2.0 ) + std::pow( p1.y() - p2.y(), 2.0 ) );
4562 totalLength += *segmentLengthIt;
4563
4564 segmentLengthIt++;
4565 p1 = p2;
4566 }
4567
4568 if ( startOffset >= 0 && totalLength <= startOffset )
4569 return QPolygonF();
4570 if ( endOffset < 0 && totalLength <= -endOffset )
4571 return QPolygonF();
4572
4573 const double startDistance = startOffset < 0 ? totalLength + startOffset : startOffset;
4574 const double endDistance = endOffset <= 0 ? totalLength + endOffset : endOffset;
4575 QPolygonF substringPoints;
4576 substringPoints.reserve( polyline.size() );
4577
4578 it = polyline.begin();
4579 segmentLengthIt = segmentLengths.begin();
4580
4581 p1 = *it++;
4582 bool foundStart = false;
4583 if ( qgsDoubleNear( startDistance, 0.0 ) || startDistance < 0 )
4584 {
4585 substringPoints << p1;
4586 foundStart = true;
4587 }
4588
4589 double distanceTraversed = 0;
4590 for ( ; it != polyline.end(); ++it )
4591 {
4592 const QPointF p2 = *it;
4593 if ( distanceTraversed < startDistance && distanceTraversed + *segmentLengthIt > startDistance )
4594 {
4595 // start point falls on this segment
4596 const double distanceToStart = startDistance - distanceTraversed;
4597 double startX, startY;
4598 QgsGeometryUtilsBase::pointOnLineWithDistance( p1.x(), p1.y(), p2.x(), p2.y(), distanceToStart, startX, startY );
4599 substringPoints << QPointF( startX, startY );
4600 foundStart = true;
4601 }
4602 if ( foundStart && ( distanceTraversed + *segmentLengthIt > endDistance ) )
4603 {
4604 // end point falls on this segment
4605 const double distanceToEnd = endDistance - distanceTraversed;
4606 double endX, endY;
4607 QgsGeometryUtilsBase::pointOnLineWithDistance( p1.x(), p1.y(), p2.x(), p2.y(), distanceToEnd, endX, endY );
4608 if ( substringPoints.last() != QPointF( endX, endY ) )
4609 substringPoints << QPointF( endX, endY );
4610 }
4611 else if ( foundStart )
4612 {
4613 if ( substringPoints.last() != QPointF( p2.x(), p2.y() ) )
4614 substringPoints << QPointF( p2.x(), p2.y() );
4615 }
4616
4617 distanceTraversed += *segmentLengthIt;
4618 if ( distanceTraversed > endDistance )
4619 break;
4620
4621 p1 = p2;
4622 segmentLengthIt++;
4623 }
4624
4625 if ( ( substringPoints.size() < 2 ) || ( substringPoints.size() == 2 && substringPoints.at( 0 ) == substringPoints.at( 1 ) ) )
4626 return QPolygonF();
4627
4628 return substringPoints;
4629}
4630
4631bool QgsSymbolLayerUtils::isSharpCorner( QPointF p1, QPointF p2, QPointF p3 )
4632{
4633 double vertexAngle = M_PI - ( std::atan2( p3.y() - p2.y(), p3.x() - p2.x() ) - std::atan2( p2.y() - p1.y(), p2.x() - p1.x() ) );
4634 vertexAngle = QgsGeometryUtilsBase::normalizedAngle( vertexAngle );
4635
4636 // extreme angles form more than 45 degree angle at a node
4637 return vertexAngle < M_PI * 135.0 / 180.0 || vertexAngle > M_PI * 225.0 / 180.0;
4638}
4639
4640void QgsSymbolLayerUtils::appendPolyline( QPolygonF &target, const QPolygonF &line )
4641{
4642 target.reserve( target.size() + line.size() );
4643 for ( const QPointF &pt : line )
4644 {
4645 if ( !target.empty() && target.last() == pt )
4646 continue;
4647
4648 target << pt;
4649 }
4650}
4651
4653{
4654 if ( fieldOrExpression.isEmpty() )
4655 return nullptr;
4656
4657 QgsExpression *expr = new QgsExpression( fieldOrExpression );
4658 if ( !expr->hasParserError() )
4659 return expr;
4660
4661 // now try with quoted field name
4662 delete expr;
4663 QgsExpression *expr2 = new QgsExpression( QgsExpression::quotedColumnRef( fieldOrExpression ) );
4664 Q_ASSERT( !expr2->hasParserError() );
4665 return expr2;
4666}
4667
4669{
4670 const QgsExpressionNode *n = expression->rootNode();
4671
4672 if ( n && n->nodeType() == QgsExpressionNode::ntColumnRef )
4673 return static_cast<const QgsExpressionNodeColumnRef *>( n )->name();
4674
4675 return expression->expression();
4676}
4677
4678QList<double> QgsSymbolLayerUtils::prettyBreaks( double minimum, double maximum, int classes )
4679{
4680 // C++ implementation of R's pretty algorithm
4681 // Based on code for determining optimal tick placement for statistical graphics
4682 // from the R statistical programming language.
4683 // Code ported from R implementation from 'labeling' R package
4684 //
4685 // Computes a sequence of about 'classes' equally spaced round values
4686 // which cover the range of values from 'minimum' to 'maximum'.
4687 // The values are chosen so that they are 1, 2 or 5 times a power of 10.
4688
4689 QList<double> breaks;
4690 if ( classes < 1 )
4691 {
4692 breaks.append( maximum );
4693 return breaks;
4694 }
4695
4696 const int minimumCount = static_cast< int >( classes ) / 3;
4697 const double shrink = 0.75;
4698 const double highBias = 1.5;
4699 const double adjustBias = 0.5 + 1.5 * highBias;
4700 const int divisions = classes;
4701 const double h = highBias;
4702 double cell;
4703 bool small = false;
4704 const double dx = maximum - minimum;
4705
4706 if ( qgsDoubleNear( dx, 0.0 ) && qgsDoubleNear( maximum, 0.0 ) )
4707 {
4708 cell = 1.0;
4709 small = true;
4710 }
4711 else
4712 {
4713 int U = 1;
4714 cell = std::max( std::fabs( minimum ), std::fabs( maximum ) );
4715 if ( adjustBias >= 1.5 * h + 0.5 )
4716 {
4717 U = 1 + ( 1.0 / ( 1 + h ) );
4718 }
4719 else
4720 {
4721 U = 1 + ( 1.5 / ( 1 + adjustBias ) );
4722 }
4723 small = dx < ( cell * U * std::max( 1, divisions ) * 1e-07 * 3.0 );
4724 }
4725
4726 if ( small )
4727 {
4728 if ( cell > 10 )
4729 {
4730 cell = 9 + cell / 10;
4731 cell = cell * shrink;
4732 }
4733 if ( minimumCount > 1 )
4734 {
4735 cell = cell / minimumCount;
4736 }
4737 }
4738 else
4739 {
4740 cell = dx;
4741 if ( divisions > 1 )
4742 {
4743 cell = cell / divisions;
4744 }
4745 }
4746 if ( cell < 20 * 1e-07 )
4747 {
4748 cell = 20 * 1e-07;
4749 }
4750
4751 const double base = std::pow( 10.0, std::floor( std::log10( cell ) ) );
4752 double unit = base;
4753 if ( ( 2 * base ) - cell < h * ( cell - unit ) )
4754 {
4755 unit = 2.0 * base;
4756 if ( ( 5 * base ) - cell < adjustBias * ( cell - unit ) )
4757 {
4758 unit = 5.0 * base;
4759 if ( ( 10.0 * base ) - cell < h * ( cell - unit ) )
4760 {
4761 unit = 10.0 * base;
4762 }
4763 }
4764 }
4765 // Maybe used to correct for the epsilon here??
4766 int start = std::floor( minimum / unit + 1e-07 );
4767 int end = std::ceil( maximum / unit - 1e-07 );
4768
4769 // Extend the range out beyond the data. Does this ever happen??
4770 while ( start * unit > minimum + ( 1e-07 * unit ) )
4771 {
4772 start = start - 1;
4773 }
4774 while ( end * unit < maximum - ( 1e-07 * unit ) )
4775 {
4776 end = end + 1;
4777 }
4778 QgsDebugMsgLevel( QStringLiteral( "pretty classes: %1" ).arg( end ), 3 );
4779
4780 // If we don't have quite enough labels, extend the range out
4781 // to make more (these labels are beyond the data :()
4782 int k = std::floor( 0.5 + end - start );
4783 if ( k < minimumCount )
4784 {
4785 k = minimumCount - k;
4786 if ( start >= 0 )
4787 {
4788 end = end + k / 2;
4789 start = start - k / 2 + k % 2;
4790 }
4791 else
4792 {
4793 start = start - k / 2;
4794 end = end + k / 2 + k % 2;
4795 }
4796 }
4797 const double minimumBreak = start * unit;
4798 //double maximumBreak = end * unit;
4799 const int count = end - start;
4800
4801 breaks.reserve( count );
4802 for ( int i = 1; i < count + 1; i++ )
4803 {
4804 breaks.append( minimumBreak + i * unit );
4805 }
4806
4807 if ( breaks.isEmpty() )
4808 return breaks;
4809
4810 if ( breaks.first() < minimum )
4811 {
4812 breaks[0] = minimum;
4813 }
4814 if ( breaks.last() > maximum )
4815 {
4816 breaks[breaks.count() - 1] = maximum;
4817 }
4818
4819 // because sometimes when number of classes is big,
4820 // break supposed to be at zero is something like -2.22045e-16
4821 if ( minimum < 0.0 && maximum > 0.0 ) //then there should be a zero somewhere
4822 {
4823 QList<double> breaksMinusZero; // compute difference "each break - 0"
4824 for ( int i = 0; i < breaks.count(); i++ )
4825 {
4826 breaksMinusZero.append( breaks[i] - 0.0 );
4827 }
4828 int posOfMin = 0;
4829 for ( int i = 1; i < breaks.count(); i++ ) // find position of minimal difference
4830 {
4831 if ( std::abs( breaksMinusZero[i] ) < std::abs( breaksMinusZero[i - 1] ) )
4832 posOfMin = i;
4833 }
4834 breaks[posOfMin] = 0.0;
4835 }
4836
4837 return breaks;
4838}
4839
4840double QgsSymbolLayerUtils::rescaleUom( double size, Qgis::RenderUnit unit, const QVariantMap &props )
4841{
4842 double scale = 1;
4843 bool roundToUnit = false;
4844 if ( unit == Qgis::RenderUnit::Unknown )
4845 {
4846 if ( props.contains( QStringLiteral( "uomScale" ) ) )
4847 {
4848 bool ok;
4849 scale = props.value( QStringLiteral( "uomScale" ) ).toDouble( &ok );
4850 if ( !ok )
4851 {
4852 return size;
4853 }
4854 }
4855 }
4856 else
4857 {
4858 if ( props.value( QStringLiteral( "uom" ) ) == QLatin1String( "http://www.opengeospatial.org/se/units/metre" ) )
4859 {
4860 switch ( unit )
4861 {
4863 scale = 0.001;
4864 break;
4866 scale = 0.00028;
4867 roundToUnit = true;
4868 break;
4869 default:
4870 scale = 1;
4871 }
4872 }
4873 else
4874 {
4875 // target is pixels
4876 switch ( unit )
4877 {
4879 scale = 1 / 0.28;
4880 roundToUnit = true;
4881 break;
4883 scale = 1 / 0.28 * 25.4;
4884 roundToUnit = true;
4885 break;
4887 scale = 90. /* dots per inch according to OGC SLD */ / 72. /* points per inch */;
4888 roundToUnit = true;
4889 break;
4891 // pixel is pixel
4892 scale = 1;
4893 break;
4896 // already handed via uom
4897 scale = 1;
4898 break;
4901 // these do not make sense and should not really reach here
4902 scale = 1;
4903 }
4904 }
4905
4906 }
4907 double rescaled = size * scale;
4908 // round to unit if the result is pixels to avoid a weird looking SLD (people often think
4909 // of pixels as integers, even if SLD allows for float values in there
4910 if ( roundToUnit )
4911 {
4912 rescaled = std::round( rescaled );
4913 }
4914 return rescaled;
4915}
4916
4917QPointF QgsSymbolLayerUtils::rescaleUom( QPointF point, Qgis::RenderUnit unit, const QVariantMap &props )
4918{
4919 const double x = rescaleUom( point.x(), unit, props );
4920 const double y = rescaleUom( point.y(), unit, props );
4921 return QPointF( x, y );
4922}
4923
4924QVector<qreal> QgsSymbolLayerUtils::rescaleUom( const QVector<qreal> &array, Qgis::RenderUnit unit, const QVariantMap &props )
4925{
4926 QVector<qreal> result;
4927 QVector<qreal>::const_iterator it = array.constBegin();
4928 for ( ; it != array.constEnd(); ++it )
4929 {
4930 result.append( rescaleUom( *it, unit, props ) );
4931 }
4932 return result;
4933}
4934
4935void QgsSymbolLayerUtils::applyScaleDependency( QDomDocument &doc, QDomElement &ruleElem, QVariantMap &props )
4936{
4937 if ( !props.value( QStringLiteral( "scaleMinDenom" ), QString() ).toString().isEmpty() )
4938 {
4939 QDomElement scaleMinDenomElem = doc.createElement( QStringLiteral( "se:MinScaleDenominator" ) );
4940 scaleMinDenomElem.appendChild( doc.createTextNode( qgsDoubleToString( props.value( QStringLiteral( "scaleMinDenom" ) ).toString().toDouble() ) ) );
4941 ruleElem.appendChild( scaleMinDenomElem );
4942 }
4943
4944 if ( !props.value( QStringLiteral( "scaleMaxDenom" ), QString() ).toString().isEmpty() )
4945 {
4946 QDomElement scaleMaxDenomElem = doc.createElement( QStringLiteral( "se:MaxScaleDenominator" ) );
4947 scaleMaxDenomElem.appendChild( doc.createTextNode( qgsDoubleToString( props.value( QStringLiteral( "scaleMaxDenom" ) ).toString().toDouble() ) ) );
4948 ruleElem.appendChild( scaleMaxDenomElem );
4949 }
4950}
4951
4952void QgsSymbolLayerUtils::mergeScaleDependencies( double mScaleMinDenom, double mScaleMaxDenom, QVariantMap &props )
4953{
4954 if ( !qgsDoubleNear( mScaleMinDenom, 0 ) )
4955 {
4956 bool ok;
4957 const double parentScaleMinDenom = props.value( QStringLiteral( "scaleMinDenom" ), QStringLiteral( "0" ) ).toString().toDouble( &ok );
4958 if ( !ok || parentScaleMinDenom <= 0 )
4959 props[ QStringLiteral( "scaleMinDenom" )] = QString::number( mScaleMinDenom );
4960 else
4961 props[ QStringLiteral( "scaleMinDenom" )] = QString::number( std::max( parentScaleMinDenom, mScaleMinDenom ) );
4962 }
4963
4964 if ( !qgsDoubleNear( mScaleMaxDenom, 0 ) )
4965 {
4966 bool ok;
4967 const double parentScaleMaxDenom = props.value( QStringLiteral( "scaleMaxDenom" ), QStringLiteral( "0" ) ).toString().toDouble( &ok );
4968 if ( !ok || parentScaleMaxDenom <= 0 )
4969 props[ QStringLiteral( "scaleMaxDenom" )] = QString::number( mScaleMaxDenom );
4970 else
4971 props[ QStringLiteral( "scaleMaxDenom" )] = QString::number( std::min( parentScaleMaxDenom, mScaleMaxDenom ) );
4972 }
4973}
4974
4975double QgsSymbolLayerUtils::sizeInPixelsFromSldUom( const QString &uom, double size )
4976{
4977 double scale = 1.0;
4978
4979 if ( uom == QLatin1String( "http://www.opengeospatial.org/se/units/metre" ) )
4980 {
4981 scale = 1.0 / 0.00028; // from meters to pixels
4982 }
4983 else if ( uom == QLatin1String( "http://www.opengeospatial.org/se/units/foot" ) )
4984 {
4985 scale = 304.8 / 0.28; // from feet to pixels
4986 }
4987 else
4988 {
4989 scale = 1.0; // from pixels to pixels (default unit)
4990 }
4991
4992 return size * scale;
4993}
4994
4995QSet<const QgsSymbolLayer *> QgsSymbolLayerUtils::toSymbolLayerPointers( const QgsFeatureRenderer *renderer, const QSet<QgsSymbolLayerId> &symbolLayerIds )
4996{
4998 class SymbolLayerVisitor : public QgsStyleEntityVisitorInterface
4999 {
5000 public:
5001 SymbolLayerVisitor( const QSet<QgsSymbolLayerId> &layerIds )
5002 : mSymbolLayerIds( layerIds )
5003 {}
5004
5005 bool visitEnter( const QgsStyleEntityVisitorInterface::Node &node ) override
5006 {
5008 {
5009 mCurrentRuleKey = node.identifier;
5010 return true;
5011 }
5012 return false;
5013 }
5014
5015 void visitSymbol( const QgsSymbol *symbol, const QString &identifier, QVector<int> rootPath )
5016 {
5017 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5018 {
5019 QVector<int> indexPath = rootPath;
5020 indexPath.append( idx );
5021 const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
5023 if ( mSymbolLayerIds.contains( QgsSymbolLayerId( mCurrentRuleKey + identifier, indexPath ) ) )
5024 {
5025 mSymbolLayers.insert( sl );
5026 }
5028
5029 const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol();
5030 if ( subSymbol )
5031 visitSymbol( subSymbol, identifier, indexPath );
5032 }
5033 }
5034
5035 bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf ) override
5036 {
5037 if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
5038 {
5039 auto symbolEntity = static_cast<const QgsStyleSymbolEntity *>( leaf.entity );
5040 if ( symbolEntity->symbol() )
5041 {
5042 visitSymbol( symbolEntity->symbol(), leaf.identifier, {} );
5043 }
5044 }
5045 return true;
5046 }
5047
5048 QString mCurrentRuleKey;
5049 const QSet<QgsSymbolLayerId> &mSymbolLayerIds;
5050 QSet<const QgsSymbolLayer *> mSymbolLayers;
5051 };
5053
5054 SymbolLayerVisitor visitor( symbolLayerIds );
5055 renderer->accept( &visitor );
5056 return visitor.mSymbolLayers;
5057}
5058
5060{
5061 class SymbolRefreshRateVisitor : public QgsStyleEntityVisitorInterface
5062 {
5063 public:
5064 SymbolRefreshRateVisitor()
5065 {}
5066
5067 bool visitEnter( const QgsStyleEntityVisitorInterface::Node &node ) override
5068 {
5070 {
5071 return true;
5072 }
5073 return false;
5074 }
5075
5076 void visitSymbol( const QgsSymbol *symbol )
5077 {
5078 // symbol may be marked as animated on a symbol level (e.g. when it implements animation
5079 // via data defined properties)
5080 if ( symbol->animationSettings().isAnimated() )
5081 {
5082 if ( symbol->animationSettings().frameRate() > refreshRate )
5083 refreshRate = symbol->animationSettings().frameRate();
5084 }
5085 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5086 {
5087 const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
5088 if ( const QgsAnimatedMarkerSymbolLayer *animatedMarker = dynamic_cast< const QgsAnimatedMarkerSymbolLayer *>( sl ) )
5089 {
5090 // this is a bit of a short cut -- if a symbol has multiple layers with different frame rates,
5091 // there's no guarantee that they will be even multiples of each other! But given we are looking for
5092 // a single frame rate for a whole renderer, it's an acceptable compromise...
5093 if ( refreshRate == -1 || ( animatedMarker->frameRate() > refreshRate ) )
5094 refreshRate = animatedMarker->frameRate();
5095 }
5096
5097 if ( const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol() )
5098 visitSymbol( subSymbol );
5099 }
5100 }
5101
5102 bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf ) override
5103 {
5104 if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
5105 {
5106 if ( QgsSymbol *symbol = qgis::down_cast<const QgsStyleSymbolEntity *>( leaf.entity )->symbol() )
5107 {
5108 visitSymbol( symbol );
5109 }
5110 }
5111 return true;
5112 }
5113
5114 double refreshRate = -1;
5115 };
5116
5117 SymbolRefreshRateVisitor visitor;
5118 renderer->accept( &visitor );
5119 return visitor.refreshRate;
5120}
5121
5122QgsSymbol *QgsSymbolLayerUtils::restrictedSizeSymbol( const QgsSymbol *s, double minSize, double maxSize, QgsRenderContext *context, double &width, double &height, bool *ok )
5123{
5124 if ( !s || !context )
5125 {
5126 return nullptr;
5127 }
5128
5129 if ( ok )
5130 *ok = true;
5131
5132 const QgsSymbolLayerList sls = s->symbolLayers();
5133 for ( const QgsSymbolLayer *sl : std::as_const( sls ) )
5134 {
5135 // geometry generators involved, there is no way to get a restricted size symbol
5136 if ( sl->type() == Qgis::SymbolType::Hybrid )
5137 {
5138 if ( ok )
5139 *ok = false;
5140
5141 return nullptr;
5142 }
5143 }
5144
5145 double size;
5146 const QgsMarkerSymbol *markerSymbol = dynamic_cast<const QgsMarkerSymbol *>( s );
5147 const QgsLineSymbol *lineSymbol = dynamic_cast<const QgsLineSymbol *>( s );
5148 if ( markerSymbol )
5149 {
5150 size = markerSymbol->size( *context );
5151 }
5152 else if ( lineSymbol )
5153 {
5154 size = lineSymbol->width( *context );
5155 }
5156 else
5157 {
5158 // cannot return a size restricted symbol but we assume there is no need
5159 // for one as the rendering will be done in the given size (different from geometry
5160 // generator where rendering will bleed outside the given area
5161 return nullptr;
5162 }
5163
5164 size /= context->scaleFactor();
5165
5166 if ( minSize > 0 && size < minSize )
5167 {
5168 size = minSize;
5169 }
5170 else if ( maxSize > 0 && size > maxSize )
5171 {
5172 size = maxSize;
5173 }
5174 else
5175 {
5176 // no need to restricted size symbol
5177 return nullptr;
5178 }
5179
5180 if ( markerSymbol )
5181 {
5182 QgsMarkerSymbol *ms = dynamic_cast<QgsMarkerSymbol *>( s->clone() );
5183 ms->setSize( size );
5185 width = size;
5186 height = size;
5187 return ms;
5188 }
5189 else if ( lineSymbol )
5190 {
5191 QgsLineSymbol *ls = dynamic_cast<QgsLineSymbol *>( s->clone() );
5192 ls->setWidth( size );
5194 height = size;
5195 return ls;
5196 }
5197
5198 return nullptr;
5199}
5200
5201QgsStringMap QgsSymbolLayerUtils::evaluatePropertiesMap( const QMap<QString, QgsProperty> &propertiesMap, const QgsExpressionContext &context )
5202{
5203 QgsStringMap properties;
5204 QMap<QString, QgsProperty>::const_iterator paramIt = propertiesMap.constBegin();
5205 for ( ; paramIt != propertiesMap.constEnd(); ++paramIt )
5206 {
5207 properties.insert( paramIt.key(), paramIt.value().valueAsString( context ) );
5208 }
5209 return properties;
5210}
5211
5212QSize QgsSymbolLayerUtils::tileSize( int width, int height, double &angleRad )
5213{
5214
5215 angleRad = std::fmod( angleRad, M_PI * 2 );
5216
5217 if ( angleRad < 0 )
5218 {
5219 angleRad += M_PI * 2;
5220 }
5221
5222 // tan with rational sin/cos
5223 struct rationalTangent
5224 {
5225 int p; // numerator
5226 int q; // denominator
5227 double angle; // "good" angle
5228 };
5229
5230#if 0
5231
5232 // This list is more granular (approx 1 degree steps) but some
5233 // values can lead to huge tiles
5234 // List of "good" angles from 0 to PI/2
5235 static const QList<rationalTangent> __rationalTangents
5236 {
5237 { 1, 57, 0.01754206006 },
5238 { 3, 86, 0.03486958155 },
5239 { 1, 19, 0.05258306161 },
5240 { 3, 43, 0.06965457373 },
5241 { 7, 80, 0.08727771295 },
5242 { 2, 19, 0.1048769387 },
5243 { 7, 57, 0.1221951707 },
5244 { 9, 64, 0.1397088743 },
5245 { 13, 82, 0.157228051 },
5246 { 3, 17, 0.174672199 },
5247 { 7, 36, 0.1920480172 },
5248 { 17, 80, 0.209385393 },
5249 { 3, 13, 0.2267988481 },
5250 { 1, 4, 0.2449786631 },
5251 { 26, 97, 0.2618852647 },
5252 { 27, 94, 0.2797041525 },
5253 { 26, 85, 0.2968446734 },
5254 { 13, 40, 0.3142318991 },
5255 { 21, 61, 0.3315541619 },
5256 { 4, 11, 0.3487710036 },
5257 { 38, 99, 0.3664967859 },
5258 { 40, 99, 0.383984624 },
5259 { 31, 73, 0.4015805401 },
5260 { 41, 92, 0.4192323938 },
5261 { 7, 15, 0.4366271598 },
5262 { 20, 41, 0.4538440015 },
5263 { 27, 53, 0.4711662643 },
5264 { 42, 79, 0.4886424026 },
5265 { 51, 92, 0.5061751436 },
5266 { 56, 97, 0.5235757641 },
5267 { 3, 5, 0.5404195003 },
5268 { 5, 8, 0.5585993153 },
5269 { 50, 77, 0.5759185996 },
5270 { 29, 43, 0.5933501462 },
5271 { 7, 10, 0.6107259644 },
5272 { 69, 95, 0.6281701124 },
5273 { 52, 69, 0.6458159195 },
5274 { 25, 32, 0.6632029927 },
5275 { 17, 21, 0.6805212247 },
5276 { 73, 87, 0.6981204504 },
5277 { 73, 84, 0.7154487784 },
5278 { 9, 10, 0.7328151018 },
5279 { 83, 89, 0.7505285818 },
5280 { 28, 29, 0.7678561033 },
5281 { 1, 1, 0.7853981634 },
5282 { 29, 28, 0.8029402235 },
5283 { 89, 83, 0.820267745 },
5284 { 10, 9, 0.837981225 },
5285 { 107, 93, 0.855284165 },
5286 { 87, 73, 0.8726758763 },
5287 { 121, 98, 0.8900374031 },
5288 { 32, 25, 0.9075933341 },
5289 { 69, 52, 0.9249804073 },
5290 { 128, 93, 0.9424647244 },
5291 { 10, 7, 0.9600703624 },
5292 { 43, 29, 0.9774461806 },
5293 { 77, 50, 0.9948777272 },
5294 { 8, 5, 1.012197011 },
5295 { 163, 98, 1.029475114 },
5296 { 168, 97, 1.047174539 },
5297 { 175, 97, 1.064668696 },
5298 { 126, 67, 1.082075603 },
5299 { 157, 80, 1.099534652 },
5300 { 203, 99, 1.117049384 },
5301 { 193, 90, 1.134452855 },
5302 { 146, 65, 1.151936673 },
5303 { 139, 59, 1.169382787 },
5304 { 99, 40, 1.186811703 },
5305 { 211, 81, 1.204257817 },
5306 { 272, 99, 1.221730164 },
5307 { 273, 94, 1.239188479 },
5308 { 277, 90, 1.25664606 },
5309 { 157, 48, 1.274088705 },
5310 { 279, 80, 1.291550147 },
5311 { 362, 97, 1.308990773 },
5312 { 373, 93, 1.326448578 },
5313 { 420, 97, 1.343823596 },
5314 { 207, 44, 1.361353157 },
5315 { 427, 83, 1.378810994 },
5316 { 414, 73, 1.396261926 },
5317 { 322, 51, 1.413716057 },
5318 { 185, 26, 1.431170275 },
5319 { 790, 97, 1.448623034 },
5320 { 333, 35, 1.466075711 },
5321 { 1063, 93, 1.483530284 },
5322 { 1330, 93, 1.500985147 },
5323 { 706, 37, 1.518436297 },
5324 { 315, 11, 1.535889876 },
5325 { 3953, 69, 1.553343002 },
5326 };
5327#endif
5328
5329 // Optimized "good" angles list, it produces small tiles but
5330 // it has approximately 10 degrees steps
5331 static const QList<rationalTangent> rationalTangents
5332 {
5333 { 1, 10, qDegreesToRadians( 5.71059 ) },
5334 { 1, 5, qDegreesToRadians( 11.3099 ) },
5335 { 1, 4, qDegreesToRadians( 14.0362 ) },
5336 { 1, 4, qDegreesToRadians( 18.4349 ) },
5337 { 1, 2, qDegreesToRadians( 26.5651 ) },
5338 { 2, 3, qDegreesToRadians( 33.6901 ) },
5339 { 1, 1, qDegreesToRadians( 45.0 ) },
5340 { 3, 2, qDegreesToRadians( 56.3099 ) },
5341 { 2, 1, qDegreesToRadians( 63.4349 ) },
5342 { 3, 1, qDegreesToRadians( 71.5651 ) },
5343 { 4, 1, qDegreesToRadians( 75.9638 ) },
5344 { 10, 1, qDegreesToRadians( 84.2894 ) },
5345 };
5346
5347 const int quadrant { static_cast<int>( angleRad / M_PI_2 ) };
5348 Q_ASSERT( quadrant >= 0 && quadrant <= 3 );
5349
5350 QSize tileSize;
5351
5352 switch ( quadrant )
5353 {
5354 case 0:
5355 {
5356 break;
5357 }
5358 case 1:
5359 {
5360 angleRad -= M_PI / 2;
5361 break;
5362 }
5363 case 2:
5364 {
5365 angleRad -= M_PI;
5366 break;
5367 }
5368 case 3:
5369 {
5370 angleRad -= M_PI + M_PI_2;
5371 break;
5372 }
5373 }
5374
5375 if ( qgsDoubleNear( angleRad, 0, 10E-3 ) )
5376 {
5377 angleRad = 0;
5378 tileSize.setWidth( width );
5379 tileSize.setHeight( height );
5380 }
5381 else if ( qgsDoubleNear( angleRad, M_PI_2, 10E-3 ) )
5382 {
5383 angleRad = M_PI_2;
5384 tileSize.setWidth( height );
5385 tileSize.setHeight( width );
5386 }
5387 else
5388 {
5389
5390 int rTanIdx = 0;
5391
5392 for ( int idx = 0; idx < rationalTangents.count(); ++idx )
5393 {
5394 const auto item = rationalTangents.at( idx );
5395 if ( qgsDoubleNear( item.angle, angleRad, 10E-3 ) || item.angle > angleRad )
5396 {
5397 rTanIdx = idx;
5398 break;
5399 }
5400 }
5401
5402 const rationalTangent bTan { rationalTangents.at( rTanIdx ) };
5403 angleRad = bTan.angle;
5404 const double k { bTan.q *height *width / std::cos( angleRad ) };
5405 const int hcfH { std::gcd( bTan.p * height, bTan.q * width ) };
5406 const int hcfW { std::gcd( bTan.q * height, bTan.p * width ) };
5407 const int W1 { static_cast<int>( std::round( k / hcfW ) ) };
5408 const int H1 { static_cast<int>( std::round( k / hcfH ) ) };
5409 tileSize.setWidth( W1 );
5410 tileSize.setHeight( H1 );
5411 }
5412
5413 switch ( quadrant )
5414 {
5415 case 0:
5416 {
5417 break;
5418 }
5419 case 1:
5420 {
5421 angleRad += M_PI / 2;
5422 const int h { tileSize.height() };
5423 tileSize.setHeight( tileSize.width() );
5424 tileSize.setWidth( h );
5425 break;
5426 }
5427 case 2:
5428 {
5429 angleRad += M_PI;
5430 break;
5431 }
5432 case 3:
5433 {
5434 angleRad += M_PI + M_PI_2;
5435 const int h { tileSize.height() };
5436 tileSize.setHeight( tileSize.width() );
5437 tileSize.setWidth( h );
5438 break;
5439 }
5440 }
5441
5442 return tileSize;
5443}
5444
5445template <typename Functor>
5446void changeSymbolLayerIds( QgsSymbolLayer *sl, Functor &&generateId )
5447{
5448 sl->setId( generateId() );
5449
5450 // recurse over sub symbols
5451 QgsSymbol *subSymbol = sl->subSymbol();
5452 if ( subSymbol )
5453 changeSymbolLayerIds( subSymbol, generateId );
5454}
5455
5456template <typename Functor>
5457void changeSymbolLayerIds( QgsSymbol *symbol, Functor &&generateId )
5458{
5459 if ( !symbol )
5460 return;
5461
5462 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5463 changeSymbolLayerIds( symbol->symbolLayer( idx ), generateId );
5464}
5465
5467{
5468 changeSymbolLayerIds( symbol, []() { return QString(); } );
5469}
5470
5472{
5473 changeSymbolLayerIds( symbolLayer, []() { return QString(); } );
5474}
5475
5477{
5478 changeSymbolLayerIds( symbolLayer, []() { return QUuid::createUuid().toString(); } );
5479}
5480
5481QVector<QgsGeometry> QgsSymbolLayerUtils::collectSymbolLayerClipGeometries( const QgsRenderContext &context, const QString &symbolLayerId, const QRectF &bounds )
5482{
5483 QVector<QgsGeometry> clipGeometries = context.symbolLayerClipGeometries( symbolLayerId );
5484 if ( clipGeometries.empty() )
5485 return {};
5486
5487 if ( bounds.isNull() )
5488 return clipGeometries;
5489
5490 const QgsRectangle boundsRect = QgsRectangle( bounds );
5491
5492 clipGeometries.erase(
5493 std::remove_if( clipGeometries.begin(), clipGeometries.end(), [&boundsRect]( const QgsGeometry & geometry )
5494 {
5495 return !geometry.boundingBoxIntersects( boundsRect );
5496 } ), clipGeometries.end() );
5497
5498 return clipGeometries;
5499}
5500
5502{
5503 changeSymbolLayerIds( symbol, []() { return QUuid::createUuid().toString(); } );
5504}
MarkerClipMode
Marker clipping modes.
Definition qgis.h:2855
@ CompletelyWithin
Render complete markers wherever the completely fall within the polygon shape.
@ NoClipping
No clipping, render complete markers.
@ Shape
Clip to polygon shape.
@ CentroidWithin
Render complete markers wherever their centroid falls within the polygon shape.
LineClipMode
Line clipping modes.
Definition qgis.h:2869
@ NoClipping
Lines are not clipped, will extend to shape's bounding box.
@ ClipPainterOnly
Applying clipping on the painter only (i.e. line endpoints will coincide with polygon bounding box,...
@ ClipToIntersection
Clip lines to intersection with polygon shape (slower) (i.e. line endpoints will coincide with polygo...
ScaleMethod
Scale methods.
Definition qgis.h:514
@ ScaleDiameter
Calculate scale by the diameter.
@ ScaleArea
Calculate scale by the area.
QFlags< SymbolLayerUserFlag > SymbolLayerUserFlags
Symbol layer user flags.
Definition qgis.h:747
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:274
@ Polygon
Polygons.
@ Miter
Use mitered joins.
RenderUnit
Rendering size units.
Definition qgis.h:4594
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size)
@ Millimeters
Millimeters.
@ Points
Points (e.g., for font sizes)
@ Unknown
Mixed or unknown units.
@ MapUnits
Map units.
@ MetersInMapUnits
Meters value as Map units.
@ Flat
Flat cap (in line with start/end of line)
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ Antialiasing
Use antialiasing while drawing.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
VertexMarkerType
Editing vertex markers, used for showing vertices during a edit operation.
Definition qgis.h:1583
@ SemiTransparentCircle
Semi-transparent circle marker.
@ Cross
Cross marker.
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:665
QFlags< SymbolFlag > SymbolFlags
Symbol flags.
Definition qgis.h:691
SymbolType
Symbol types.
Definition qgis.h:500
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
@ Hybrid
Hybrid symbol.
@ RendererShouldUseSymbolLevels
If present, indicates that a QgsFeatureRenderer using the symbol should use symbol levels for best re...
@ LineString
LineString.
@ Polygon
Polygon.
@ MultiPolygon
MultiPolygon.
@ MultiLineString
MultiLineString.
SymbolCoordinateReference
Symbol coordinate reference modes.
Definition qgis.h:2813
@ Feature
Relative to feature/shape being rendered.
@ Viewport
Relative to the whole viewport/output device.
virtual bool readXml(const QDomElement &collectionElem, const QgsPropertiesDefinition &definitions)
Reads property collection state from an XML element.
virtual bool writeXml(QDomElement &collectionElem, const QgsPropertiesDefinition &definitions) const
Writes the current state of the property collection into an XML element.
Animated marker symbol layer class.
static QgsPaintEffectRegistry * paintEffectRegistry()
Returns the application's paint effect registry, used for managing paint effects.
static QgsSymbolLayerRegistry * symbolLayerRegistry()
Returns the application's symbol layer registry, used for managing symbol layers.
static QStringList svgPaths()
Returns the paths to svg directories.
HeadType
Possible head types.
ArrowType
Possible arrow types.
static QString typeString()
Returns the string identifier for QgsColorBrewerColorRamp.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Returns a new QgsColorBrewerColorRamp color ramp created using the properties encoded in a string map...
Abstract base class for color ramps.
virtual QColor color(double value) const =0
Returns the color corresponding to a specified value.
virtual double value(int index) const =0
Returns relative value between [0,1] of color at specified index.
virtual QVariantMap properties() const =0
Returns a string map containing all the color ramp's properties.
virtual QString type() const =0
Returns a string representing the color ramp type.
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.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates the symbol layer.
static QString typeString()
Returns the string identifier for QgsCptCityColorRamp.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
bool hasFeature() const
Returns true if the context has a feature associated with it.
An expression node which takes it value from a feature's field.
Abstract base class for all nodes that can appear in an expression.
virtual QgsExpressionNode::NodeType nodeType() const =0
Gets the type of this node.
Class for parsing and evaluation of expressions (formerly called "search strings").
QString expression() const
Returns the original, unmodified expression string.
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QString parserErrorString() const
Returns parser error.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
const QgsExpressionNode * rootNode() const
Returns the root node of the expression.
virtual bool accept(QgsStyleEntityVisitorInterface *visitor) const
Accepts the specified symbology visitor, causing it to visit all symbols associated with the renderer...
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
static void pointOnLineWithDistance(double x1, double y1, double x2, double y2, double distance, double &x, double &y, double *z1=nullptr, double *z2=nullptr, double *z=nullptr, double *m1=nullptr, double *m2=nullptr, double *m=nullptr)
Calculates the point a specified distance from (x1, y1) toward a second point (x2,...
static double normalizedAngle(double angle)
Ensures that an angle is in the range 0 <= angle < 2 pi.
A geometry is the spatial representation of a feature.
QgsMultiPolygonXY asMultiPolygon() const
Returns the contents of the geometry as a multi-polygon.
QgsGeometry offsetCurve(double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit) const
Returns an offset line at a given distance and side from an input line.
QgsGeometry pointOnSurface() const
Returns a point guaranteed to lie on the surface of a geometry.
static QgsGeometry fromPolylineXY(const QgsPolylineXY &polyline)
Creates a new LineString geometry from a list of QgsPointXY points.
QgsPolygonXY asPolygon() const
Returns the contents of the geometry as a polygon.
QgsPolylineXY asPolyline() const
Returns the contents of the geometry as a polyline.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
Qgis::GeometryOperationResult addRing(const QVector< QgsPointXY > &ring)
Adds a new ring to this geometry.
QgsMultiPolylineXY asMultiPolyline() const
Returns the contents of the geometry as a multi-linestring.
static QgsGeometry fromPolygonXY(const QgsPolygonXY &polygon)
Creates a new geometry from a QgsPolygonXY.
QgsGeometry buffer(double distance, int segments) const
Returns a buffer region around this geometry having the given width and with a specified number of se...
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsColorRamp from a map of properties.
static QString typeString()
Returns the string identifier for QgsGradientColorRamp.
Represents a patch shape for use in map legends.
static QString typeString()
Returns the string identifier for QgsLimitedRandomColorRamp.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Returns a new QgsLimitedRandomColorRamp color ramp created using the properties encoded in a string m...
const QgsMapUnitScale & widthMapUnitScale() const
@ AllRings
Render both exterior and interior rings.
RenderRingFilter ringFilter() const
Returns the line symbol layer's ring filter, which controls which rings are rendered when the line sy...
virtual double width() const
Returns the estimated width for the line symbol layer.
double offset() const
Returns the line's offset.
Qgis::RenderUnit widthUnit() const
Returns the units for the line's width.
A line symbol type, for rendering LineString and MultiLineString geometries.
void setWidthUnit(Qgis::RenderUnit unit) const
Sets the width units for the whole symbol (including all symbol layers).
void setWidth(double width) const
Sets the width for the whole line symbol.
double width() const
Returns the estimated width for the whole symbol, which is the maximum width of all marker symbol lay...
Base class for all map layer types.
Definition qgsmaplayer.h:75
Struct for storing maximum and minimum scales for measurements in map units.
bool minSizeMMEnabled
Whether the minimum size in mm should be respected.
double maxScale
The maximum scale, or 0.0 if unset.
double minScale
The minimum scale, or 0.0 if unset.
double maxSizeMM
The maximum size in millimeters, or 0.0 if unset.
bool maxSizeMMEnabled
Whether the maximum size in mm should be respected.
double minSizeMM
The minimum size in millimeters, or 0.0 if unset.
A marker symbol type, for rendering Point and MultiPoint geometries.
void setSize(double size) const
Sets the size for the whole symbol.
double size() const
Returns the estimated size for the whole symbol, which is the maximum size of all marker symbol layer...
void setSizeUnit(Qgis::RenderUnit unit) const
Sets the size units for the whole symbol (including all symbol layers).
static QDomElement elseFilterExpression(QDomDocument &doc)
Creates an ElseFilter from doc.
static QDomElement expressionToOgcExpression(const QgsExpression &exp, QDomDocument &doc, QString *errorMessage=nullptr, bool requiresFilterElement=false)
Creates an OGC expression XML element from the exp expression with default values for the geometry na...
static QDomElement expressionToOgcFilter(const QgsExpression &exp, QDomDocument &doc, QString *errorMessage=nullptr)
Creates OGC filter XML element.
static QgsExpression * expressionFromOgcFilter(const QDomElement &element, QgsVectorLayer *layer=nullptr)
Parse XML with OGC filter into QGIS expression.
static bool isDefaultStack(QgsPaintEffect *effect)
Tests whether a paint effect matches the default effects stack.
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.
Resolves relative paths into absolute paths and vice versa.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
A class to represent a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
void setY(double y)
Sets the point's y-coordinate.
Definition qgspoint.h:343
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:332
double x
Definition qgspoint.h:52
double y
Definition qgspoint.h:53
static QString typeString()
Returns the string identifier for QgsPresetSchemeColorRamp.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Returns a new QgsPresetSchemeColorRamp color ramp created using the properties encoded in a string ma...
A grouped map of multiple QgsProperty objects, each referenced by a integer key value.
QSet< int > propertyKeys() const final
Returns a list of property keys contained within the collection.
QgsProperty property(int key) const final
Returns a matching property from the collection, if one exists.
A store for object properties.
bool isProjectColor() const
Returns true if the property is set to a linked project color.
bool isActive() const
Returns whether the property is currently active.
void setActive(bool active)
Sets whether the property is currently active.
The class is used as a container of context for various read/write operations on other objects.
const QgsPathResolver & pathResolver() const
Returns path resolver for conversion between relative and absolute paths.
A rectangle specified with double values.
Contains information about the context of a rendering operation.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
void setDevicePixelRatio(float ratio)
Sets the device pixel ratio.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
QgsExpressionContext & expressionContext()
Gets the expression context.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QVector< QgsGeometry > symbolLayerClipGeometries(const QString &symbolLayerId) const
Returns clipping geometries to be applied to the symbolLayer before rendering.
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
A class for filling symbols with a repeated SVG file.
Stores properties relating to a screen.
double devicePixelRatio() const
Returns the ratio between physical pixels and device-independent pixels for the screen.
bool isValid() const
Returns true if the properties are valid.
void updateRenderContextForScreen(QgsRenderContext &context) const
Updates the settings in a render context to match the screen settings.
void setStrokeWidthMapUnitScale(const QgsMapUnitScale &scale)
void setStrokeWidthUnit(Qgis::RenderUnit unit)
Sets the units for the width of the fill's stroke.
void setPenJoinStyle(Qt::PenJoinStyle style)
void setStrokeWidth(double strokeWidth)
void setStrokeStyle(Qt::PenStyle strokeStyle)
void setStrokeColor(const QColor &strokeColor) override
Sets the stroke color for the symbol layer.
A simple line symbol layer, which renders lines using a line in a variety of styles (e....
bool tweakDashPatternOnCorners() const
Returns true if dash patterns tweaks should be applied on sharp corners, to ensure that a double-leng...
Qt::PenJoinStyle penJoinStyle() const
Returns the pen join style used to render the line (e.g.
double trimDistanceStart() const
Returns the trim distance for the start of the line, which dictates a length from the start of the li...
double trimDistanceEnd() const
Returns the trim distance for the end of the line, which dictates a length from the end of the line a...
bool useCustomDashPattern() const
Returns true if the line uses a custom dash pattern.
Qt::PenStyle penStyle() const
Returns the pen style used to render the line (e.g.
double dashPatternOffset() const
Returns the dash pattern offset, which dictates how far along the dash pattern the pattern should sta...
bool drawInsidePolygon() const
Returns true if the line should only be drawn inside polygons, and any portion of the line which fall...
bool alignDashPattern() const
Returns true if dash patterns should be aligned to the start and end of lines, by applying subtle twe...
virtual QgsStyle::StyleEntity type() const =0
Returns the type of style entity.
An interface for classes which can visit style entity (e.g.
@ SymbolRule
Rule based symbology or label child rule.
A symbol entity for QgsStyle databases.
Definition qgsstyle.h:1376
@ SymbolEntity
Symbols.
Definition qgsstyle.h:184
bool isAnimated() const
Returns true if the symbol is animated.
Definition qgssymbol.h:64
void setIsAnimated(bool animated)
Sets whether the symbol is animated.
Definition qgssymbol.h:53
void setFrameRate(double rate)
Sets the symbol animation frame rate (in frames per second).
Definition qgssymbol.h:71
double frameRate() const
Returns the symbol animation frame rate (in frames per second).
Definition qgssymbol.h:78
We may need stable references to symbol layers, when pointers to symbol layers is not usable (when a ...
QgsSymbolLayer * createSymbolLayerFromSld(const QString &name, QDomElement &element) const
create a new instance of symbol layer given symbol layer name and SLD
QgsSymbolLayer * createSymbolLayer(const QString &name, const QVariantMap &properties=QVariantMap()) const
create a new instance of symbol layer given symbol layer name and properties
void resolvePaths(const QString &name, QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving) const
Resolve paths in properties of a particular symbol layer.
void resolveFonts(const QString &name, QVariantMap &properties, const QgsReadWriteContext &context) const
Resolve fonts from the properties of a particular symbol layer.
static bool externalMarkerFromSld(QDomElement &element, QString &path, QString &format, int &markIndex, QColor &color, double &size)
static QColor parseColor(const QString &colorStr, bool strictEval=false)
Attempts to parse a string as a color using a variety of common formats, including hex codes,...
static bool rotationFromSldElement(QDomElement &element, QString &rotationFunc)
static void createAnchorPointElement(QDomDocument &doc, QDomElement &element, QPointF anchor)
Creates a SE 1.1 anchor point element as a child of the specified element.
static void sortVariantList(QList< QVariant > &list, Qt::SortOrder order)
Sorts the passed list in requested order.
static Qgis::MarkerClipMode decodeMarkerClipMode(const QString &string, bool *ok=nullptr)
Decodes a string representing a marker clip mode.
static QPicture symbolLayerPreviewPicture(const QgsSymbolLayer *layer, Qgis::RenderUnit units, QSize size, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::SymbolType parentSymbolType=Qgis::SymbolType::Hybrid)
Draws a symbol layer preview to a QPicture.
static bool hasExternalGraphic(QDomElement &element)
Checks if element contains an ExternalGraphic element with format "image/svg+xml".
static QString encodePenStyle(Qt::PenStyle style)
static bool needMarkerLine(QDomElement &element)
static QVector< qreal > decodeSldRealVector(const QString &s)
static bool needLinePatternFill(QDomElement &element)
static void clearSymbolLayerIds(QgsSymbol *symbol)
Remove recursively unique id from all symbol symbol layers and set an empty string instead.
static QString encodeSldBrushStyle(Qt::BrushStyle style)
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QgsArrowSymbolLayer::HeadType decodeArrowHeadType(const QVariant &value, bool *ok=nullptr)
Decodes a value representing an arrow head type.
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static Q_DECL_DEPRECATED QSet< const QgsSymbolLayer * > toSymbolLayerPointers(const QgsFeatureRenderer *renderer, const QSet< QgsSymbolLayerId > &symbolLayerIds)
Converts a set of symbol layer id to a set of pointers to actual symbol layers carried by the feature...
static QVariant colorRampToVariant(const QString &name, QgsColorRamp *ramp)
Saves a color ramp to a QVariantMap, wrapped in a QVariant.
static void applyScaleDependency(QDomDocument &doc, QDomElement &ruleElem, QVariantMap &props)
Checks if the properties contain scaleMinDenom and scaleMaxDenom, if available, they are added into t...
static QgsSymbol * symbolFromMimeData(const QMimeData *data)
Attempts to parse mime data as a symbol.
static QgsStringMap evaluatePropertiesMap(const QMap< QString, QgsProperty > &propertiesMap, const QgsExpressionContext &context)
Evaluates a map of properties using the given context and returns a variant map with evaluated expres...
static void drawVertexMarker(double x, double y, QPainter &p, Qgis::VertexMarkerType type, int markerSize)
Draws a vertex symbol at (painter) coordinates x, y.
static bool createExpressionElement(QDomDocument &doc, QDomElement &element, const QString &function)
Creates a OGC Expression element based on the provided function expression.
static bool displacementFromSldElement(QDomElement &element, QPointF &offset)
static bool hasWellKnownMark(QDomElement &element)
static QString getSvgParametricPath(const QString &basePath, const QColor &fillColor, const QColor &strokeColor, double strokeWidth)
Encodes a reference to a parametric SVG into a path with parameters according to the SVG Parameters s...
static bool createFunctionElement(QDomDocument &doc, QDomElement &element, const QString &function)
static QColor decodeColor(const QString &str)
static bool onlineResourceFromSldElement(QDomElement &element, QString &path, QString &format)
static QPointF polygonCentroid(const QPolygonF &points)
Calculate the centroid point of a QPolygonF.
static QIcon colorRampPreviewIcon(QgsColorRamp *ramp, QSize size, int padding=0)
Returns an icon preview for a color ramp.
static QString encodeBrushStyle(Qt::BrushStyle style)
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol's name from its path.
static QgsColorRamp * loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
static QPixmap colorRampPreviewPixmap(QgsColorRamp *ramp, QSize size, int padding=0, Qt::Orientation direction=Qt::Horizontal, bool flipDirection=false, bool drawTransparentBackground=true)
Returns a pixmap preview for a color ramp.
static QString encodeSldAlpha(int alpha)
static void externalGraphicToSld(QDomDocument &doc, QDomElement &element, const QString &path, const QString &mime, const QColor &color, double size=-1)
static QPointF polygonPointOnSurface(const QPolygonF &points, const QVector< QPolygonF > *rings=nullptr)
Calculate a point on the surface of a QPolygonF.
static void blurImageInPlace(QImage &image, QRect rect, int radius, bool alphaOnly)
Blurs an image in place, e.g. creating Qt-independent drop shadows.
static QList< double > prettyBreaks(double minimum, double maximum, int classes)
Computes a sequence of about 'classes' equally spaced round values which cover the range of values fr...
static QPointF toPoint(const QVariant &value, bool *ok=nullptr)
Converts a value to a point.
static void premultiplyColor(QColor &rgb, int alpha)
Converts a QColor into a premultiplied ARGB QColor value using a specified alpha value.
static void saveProperties(QVariantMap props, QDomDocument &doc, QDomElement &element)
Saves the map of properties to XML.
static void multiplyImageOpacity(QImage *image, qreal opacity)
Multiplies opacity of image pixel values with a (global) transparency value.
static bool functionFromSldElement(QDomElement &element, QString &function)
static bool saveColorsToGpl(QFile &file, const QString &paletteName, const QgsNamedColorList &colors)
Exports colors to a gpl GIMP palette file.
static QColor parseColorWithAlpha(const QString &colorStr, bool &containsAlpha, bool strictEval=false)
Attempts to parse a string as a color using a variety of common formats, including hex codes,...
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QSizeF toSize(const QVariant &value, bool *ok=nullptr)
Converts a value to a size.
static double rescaleUom(double size, Qgis::RenderUnit unit, const QVariantMap &props)
Rescales the given size based on the uomScale found in the props, if any is found,...
static bool needEllipseMarker(QDomElement &element)
static QgsNamedColorList colorListFromMimeData(const QMimeData *data)
Attempts to parse mime data as a list of named colors.
static bool isSharpCorner(QPointF p1, QPointF p2, QPointF p3)
Returns true if the angle formed by the line p1 - p2 - p3 forms a "sharp" corner.
static QString ogrFeatureStylePen(double width, double mmScaleFactor, double mapUnitsScaleFactor, const QColor &c, Qt::PenJoinStyle joinStyle=Qt::MiterJoin, Qt::PenCapStyle capStyle=Qt::FlatCap, double offset=0.0, const QVector< qreal > *dashPattern=nullptr)
Create ogr feature style string for pen.
static Qt::PenCapStyle decodePenCapStyle(const QString &str)
static double rendererFrameRate(const QgsFeatureRenderer *renderer)
Calculates the frame rate (in frames per second) at which the given renderer must be redrawn.
static QgsStringMap getSvgParameterList(QDomElement &element)
static bool needSvgFill(QDomElement &element)
static bool createSymbolLayerListFromSld(QDomElement &element, Qgis::GeometryType geomType, QList< QgsSymbolLayer * > &layers)
Creates a symbol layer list from a DOM element.
static bool externalGraphicFromSld(QDomElement &element, QString &path, QString &mime, QColor &color, double &size)
static void parametricSvgToSld(QDomDocument &doc, QDomElement &graphicElem, const QString &path, const QColor &fillColor, double size, const QColor &strokeColor, double strokeWidth)
Encodes a reference to a parametric SVG into SLD, as a succession of parametric SVG using URL paramet...
static QIcon symbolLayerPreviewIcon(const QgsSymbolLayer *layer, Qgis::RenderUnit u, QSize size, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::SymbolType parentSymbolType=Qgis::SymbolType::Hybrid, QgsMapLayer *mapLayer=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Draws a symbol layer preview to an icon.
static QString encodeSldLineCapStyle(Qt::PenCapStyle style)
static QString encodeSldUom(Qgis::RenderUnit unit, double *scaleFactor)
Encodes a render unit into an SLD unit of measure string.
static QVector< qreal > decodeRealVector(const QString &s)
static bool lineFromSld(QDomElement &element, Qt::PenStyle &penStyle, QColor &color, double &width, Qt::PenJoinStyle *penJoinStyle=nullptr, Qt::PenCapStyle *penCapStyle=nullptr, QVector< qreal > *customDashPattern=nullptr, double *dashOffset=nullptr)
static QPainter::CompositionMode decodeBlendMode(const QString &s)
static Qgis::ScaleMethod decodeScaleMethod(const QString &str)
Decodes a symbol scale method from a string.
static void createOpacityElement(QDomDocument &doc, QDomElement &element, const QString &alphaFunc)
static QString ogrFeatureStyleBrush(const QColor &fillColr)
Create ogr feature style string for brush.
static bool pointInPolygon(const QPolygonF &points, QPointF point)
Calculate whether a point is within of a QPolygonF.
static QStringList listSvgFiles()
Returns a list of all available svg files.
static QString encodeLineClipMode(Qgis::LineClipMode mode)
Encodes a line clip mode to a string.
static QPixmap symbolPreviewPixmap(const QgsSymbol *symbol, QSize size, int padding=0, QgsRenderContext *customContext=nullptr, bool selected=false, const QgsExpressionContext *expressionContext=nullptr, const QgsLegendPatchShape *shape=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Returns a pixmap preview for a color ramp.
static bool convertPolygonSymbolizerToPointMarker(QDomElement &element, QList< QgsSymbolLayer * > &layerList)
Converts a polygon symbolizer element to a list of marker symbol layers.
static Qgis::LineClipMode decodeLineClipMode(const QString &string, bool *ok=nullptr)
Decodes a string representing a line clip mode.
static QStringList listSvgFilesAt(const QString &directory)
Returns a list of svg files at the specified directory.
static bool needFontMarker(QDomElement &element)
static QString encodePenCapStyle(Qt::PenCapStyle style)
static QPointF pointOnLineWithDistance(QPointF startPoint, QPointF directionPoint, double distance)
Returns a point on the line from startPoint to directionPoint that is a certain distance away from th...
static QFont::Style decodeSldFontStyle(const QString &str)
static QSize tileSize(int width, int height, double &angleRad)
Calculate the minimum size in pixels of a symbol tile given the symbol width and height and the symbo...
static QString fieldOrExpressionFromExpression(QgsExpression *expression)
Returns a field name if the whole expression is just a name of the field .
static QDomElement saveColorRamp(const QString &name, QgsColorRamp *ramp, QDomDocument &doc)
Encodes a color ramp's settings to an XML element.
static bool opacityFromSldElement(QDomElement &element, QString &alphaFunc)
static QString encodeSldFontWeight(int weight)
static void externalMarkerToSld(QDomDocument &doc, QDomElement &element, const QString &path, const QString &format, int *markIndex=nullptr, const QColor &color=QColor(), double size=-1)
static QMimeData * colorListToMimeData(const QgsNamedColorList &colorList, bool allFormats=true)
Creates mime data from a list of named colors.
static Qt::BrushStyle decodeBrushStyle(const QString &str)
static void lineToSld(QDomDocument &doc, QDomElement &element, Qt::PenStyle penStyle, const QColor &color, double width=-1, const Qt::PenJoinStyle *penJoinStyle=nullptr, const Qt::PenCapStyle *penCapStyle=nullptr, const QVector< qreal > *customDashPattern=nullptr, double dashOffset=0.0)
static QVector< QgsGeometry > collectSymbolLayerClipGeometries(const QgsRenderContext &context, const QString &symbolLayerId, const QRectF &bounds)
Returns a list of the symbol layer clip geometries to be used for the symbol layer with the specified...
static Qt::PenCapStyle decodeSldLineCapStyle(const QString &str)
static QgsNamedColorList importColorsFromGpl(QFile &file, bool &ok, QString &name)
Imports colors from a gpl GIMP palette file.
static QString encodeSize(QSizeF size)
Encodes a QSizeF to a string.
static QDomElement createVendorOptionElement(QDomDocument &doc, const QString &name, const QString &value)
static double sizeInPixelsFromSldUom(const QString &uom, double size)
Returns the size scaled in pixels according to the uom attribute.
static void appendPolyline(QPolygonF &target, const QPolygonF &line)
Appends a polyline line to an existing target polyline.
static bool wellKnownMarkerFromSld(QDomElement &element, QString &name, QColor &color, QColor &strokeColor, Qt::PenStyle &strokeStyle, double &strokeWidth, double &size)
static void mergeScaleDependencies(double mScaleMinDenom, double mScaleMaxDenom, QVariantMap &props)
Merges the local scale limits, if any, with the ones already in the map, if any.
static QgsSymbol * loadSymbol(const QDomElement &element, const QgsReadWriteContext &context)
Attempts to load a symbol from a DOM element.
static QgsSymbol * restrictedSizeSymbol(const QgsSymbol *s, double minSize, double maxSize, QgsRenderContext *context, double &width, double &height, bool *ok=nullptr)
Creates a new symbol with size restricted to min/max size if original size is out of min/max range.
static QString colorToName(const QColor &color)
Returns a friendly display name for a color.
static int decodeSldAlpha(const QString &str)
static QString encodeSldLineJoinStyle(Qt::PenJoinStyle style)
static void createDisplacementElement(QDomDocument &doc, QDomElement &element, QPointF offset)
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static void drawStippledBackground(QPainter *painter, QRect rect)
static QList< QColor > parseColorList(const QString &colorStr)
Attempts to parse a string as a list of colors using a variety of common formats, including hex codes...
static QString encodeColor(const QColor &color)
static QgsSymbolLayer * loadSymbolLayer(QDomElement &element, const QgsReadWriteContext &context)
Reads and returns symbol layer from XML. Caller is responsible for deleting the returned object.
static Qt::PenJoinStyle decodeSldLineJoinStyle(const QString &str)
static QVariantMap parseProperties(const QDomElement &element)
Parses the properties from XML and returns a map.
static bool fillFromSld(QDomElement &element, Qt::BrushStyle &brushStyle, QColor &color)
static QMimeData * symbolToMimeData(const QgsSymbol *symbol)
Creates new mime data from a symbol.
static QString encodeSldFontStyle(QFont::Style style)
static QColor colorFromMimeData(const QMimeData *data, bool &hasAlpha)
Attempts to parse mime data as a color.
static int decodeSldFontWeight(const QString &str)
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
static void fillToSld(QDomDocument &doc, QDomElement &element, Qt::BrushStyle brushStyle, const QColor &color=QColor())
static double polylineLength(const QPolygonF &polyline)
Returns the total length of a polyline.
static bool hasExternalGraphicV2(QDomElement &element, const QString format=QString())
Checks if element contains an ExternalGraphic element, if the optional format is specified it will al...
static Qgis::RenderUnit decodeSldUom(const QString &str, double *scaleFactor=nullptr)
Decodes a SLD unit of measure string to a render unit.
static void createGeometryElement(QDomDocument &doc, QDomElement &element, const QString &geomFunc)
static QgsArrowSymbolLayer::ArrowType decodeArrowType(const QVariant &value, bool *ok=nullptr)
Decodes a value representing an arrow type.
static bool needSvgMarker(QDomElement &element)
static void clearSymbolMap(QgsSymbolMap &symbols)
static Qt::BrushStyle decodeSldBrushStyle(const QString &str)
static void resetSymbolLayerIds(QgsSymbol *symbol)
Regenerate recursively unique id from all symbol symbol layers.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
static void wellKnownMarkerToSld(QDomDocument &doc, QDomElement &element, const QString &name, const QColor &color, const QColor &strokeColor, Qt::PenStyle strokeStyle, double strokeWidth=-1, double size=-1)
static QString symbolProperties(QgsSymbol *symbol)
Returns a string representing the symbol.
static bool geometryFromSldElement(QDomElement &element, QString &geomFunc)
static QString encodeScaleMethod(Qgis::ScaleMethod scaleMethod)
Encodes a symbol scale method to a string.
static void createOnlineResourceElement(QDomDocument &doc, QDomElement &element, const QString &path, const QString &format)
static Qt::PenStyle decodePenStyle(const QString &str)
static void createRotationElement(QDomDocument &doc, QDomElement &element, const QString &rotationFunc)
static Qgis::SymbolCoordinateReference decodeCoordinateReference(const QString &string, bool *ok=nullptr)
Decodes a string representing a symbol coordinate reference mode.
static QgsSymbolMap loadSymbols(QDomElement &element, const QgsReadWriteContext &context)
Reads a collection of symbols from XML and returns them in a map. Caller is responsible for deleting ...
static QString encodePoint(QPointF point)
Encodes a QPointF to a string.
static void labelTextToSld(QDomDocument &doc, QDomElement &element, const QString &label, const QFont &font, const QColor &color=QColor(), double size=-1)
static QgsSymbolLayer * createMarkerLayerFromSld(QDomElement &element)
static QDomElement saveSymbols(QgsSymbolMap &symbols, const QString &tagName, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a collection of symbols to XML with specified tagName for the top-level element.
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
static QgsStringMap getVendorOptionList(QDomElement &element)
static QIcon symbolPreviewIcon(const QgsSymbol *symbol, QSize size, int padding=0, QgsLegendPatchShape *shape=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Returns an icon preview for a color ramp.
static QMimeData * colorToMimeData(const QColor &color)
Creates mime data from a color.
static bool condenseFillAndOutline(QgsFillSymbolLayer *fill, QgsLineSymbolLayer *outline)
Attempts to condense a fill and outline layer, by moving the outline layer to the fill symbol's strok...
static QPointF decodePoint(const QString &string)
Decodes a QSizeF from a string.
static QPolygonF polylineSubstring(const QPolygonF &polyline, double startOffset, double endOffset)
Returns the substring of a polyline which starts at startOffset from the beginning of the line and en...
static QgsSymbolLayer * createLineLayerFromSld(QDomElement &element)
static bool needPointPatternFill(QDomElement &element)
static QString encodeSldRealVector(const QVector< qreal > &v)
static QString encodeCoordinateReference(Qgis::SymbolCoordinateReference coordinateReference)
Encodes a symbol coordinate reference mode to a string.
static QgsSymbolLayer * createFillLayerFromSld(QDomElement &element)
static bool needRasterImageFill(QDomElement &element)
Checks if element contains a graphic fill with a raster image of type PNG, JPEG or GIF.
static QDomElement createSvgParameterElement(QDomDocument &doc, const QString &name, const QString &value)
static QString encodeMarkerClipMode(Qgis::MarkerClipMode mode)
Encodes a marker clip mode to a string.
static QgsExpression * fieldOrExpressionToExpression(const QString &fieldOrExpression)
Returns a new valid expression instance for given field or expression string.
static QSizeF decodeSize(const QString &string)
Decodes a QSizeF from a string.
static QString encodeRealVector(const QVector< qreal > &v)
virtual QgsSymbolLayer * clone() const =0
Shall be reimplemented by subclasses to create a deep copy of the instance.
virtual bool setSubSymbol(QgsSymbol *symbol)
Sets layer's subsymbol. takes ownership of the passed symbol.
void setId(const QString &id)
Set symbol layer identifier This id has to be unique in the whole project.
bool isLocked() const
Returns true if the symbol layer colors are locked and the layer will ignore any symbol-level color c...
Property
Data definable properties.
void setPaintEffect(QgsPaintEffect *effect)
Sets the current paint effect for the layer.
void setRenderingPass(int renderingPass)
Specifies the rendering pass in which this symbol layer should be rendered.
virtual double estimateMaxBleed(const QgsRenderContext &context) const
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QgsPaintEffect * paintEffect() const
Returns the current paint effect for the layer.
void setEnabled(bool enabled)
Sets whether symbol layer is enabled and should be drawn.
virtual QVariantMap properties() const =0
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void setUserFlags(Qgis::SymbolLayerUserFlags flags)
Sets user-controlled flags which control the symbol layer's behavior.
bool enabled() const
Returns true if symbol layer is enabled and will be drawn.
virtual QString layerType() const =0
Returns a string that represents this layer type.
int renderingPass() const
Specifies the rendering pass in which this symbol layer should be rendered.
QString id() const
Returns symbol layer identifier This id is unique in the whole project.
virtual void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the layer.
virtual QgsSymbol * subSymbol()
Returns the symbol's sub symbol, if present.
Qgis::SymbolLayerUserFlags userFlags() const
Returns user-controlled flags which control the symbol layer's behavior.
virtual QColor color() const
Returns the "representative" color of the symbol layer.
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the symbol layer property definitions.
void setLocked(bool locked)
Sets whether the layer's colors are locked.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer's property collection, used for data defined overrides.
virtual bool hasDataDefinedProperties() const
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void setOriginalGeometryType(Qgis::GeometryType type)
Sets the geometry type for the original feature geometry being rendered.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:94
void setOutputUnit(Qgis::RenderUnit unit) const
Sets the units to use for sizes and widths within the symbol.
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol's property collection, used for data defined overrides.
Definition qgssymbol.h:620
QgsSymbolAnimationSettings & animationSettings()
Returns a reference to the symbol animation settings.
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the symbol property definitions.
Qgis::SymbolFlags flags() const
Returns flags for the symbol.
Definition qgssymbol.h:530
qreal opacity() const
Returns the opacity for the symbol.
Definition qgssymbol.h:495
void setMapUnitScale(const QgsMapUnitScale &scale) const
Sets the map unit scale for the symbol.
bool clipFeaturesToExtent() const
Returns whether features drawn by the symbol will be clipped to the render context's extent.
Definition qgssymbol.h:550
void setFlags(Qgis::SymbolFlags flags)
Sets flags for the symbol.
Definition qgssymbol.h:522
bool hasDataDefinedProperties() const
Returns whether the symbol utilizes any data defined properties.
QgsSymbolLayerList symbolLayers() const
Returns the list of symbol layers contained in the symbol.
Definition qgssymbol.h:166
void setOpacity(qreal opacity)
Sets the opacity for the symbol.
Definition qgssymbol.h:502
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition qgssymbol.h:215
QColor color() const
Returns the symbol's color.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:156
bool forceRHR() const
Returns true if polygon features drawn by the symbol will be reoriented to follow the standard right-...
Definition qgssymbol.h:572
void setClipFeaturesToExtent(bool clipFeaturesToExtent)
Sets whether features drawn by the symbol should be clipped to the render context's extent.
Definition qgssymbol.h:540
void setForceRHR(bool force)
Sets whether polygon features drawn by the symbol should be reoriented to follow the standard right-h...
Definition qgssymbol.h:561
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
static QDomElement writeVariant(const QVariant &value, QDomDocument &doc)
Write a QVariant to a QDomElement.
static QVariant readVariant(const QDomElement &element)
Read a QVariant from a QDomElement.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define str(x)
Definition qgis.cpp:38
bool qgsVariantLessThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is less than the second.
Definition qgis.cpp:120
bool qgsVariantGreaterThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is greater than the second.
Definition qgis.cpp:188
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6229
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:5569
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:5901
T qgsFlagKeysToValue(const QString &keys, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given keys of a flag.
Definition qgis.h:5923
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6228
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5652
QVector< QgsPolylineXY > QgsPolygonXY
Polygon: first item of the list is outer ring, inner rings (if any) start from second item.
Definition qgsgeometry.h:74
QVector< QgsPolylineXY > QgsMultiPolylineXY
A collection of QgsPolylines that share a common collection of attributes.
Definition qgsgeometry.h:84
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition qgsgeometry.h:62
QVector< QgsPolygonXY > QgsMultiPolygonXY
A collection of QgsPolygons that share a common collection of attributes.
Definition qgsgeometry.h:91
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
QList< QPolygonF > offsetLine(QPolygonF polyline, double dist, Qgis::GeometryType geometryType)
calculate geometry shifted by a specified distance
void changeSymbolLayerIds(QgsSymbolLayer *sl, Functor &&generateId)
QList< QPair< QColor, QString > > QgsNamedColorList
QMap< QString, QgsSymbol * > QgsSymbolMap
QMap< QString, QString > QgsStringMap
Contains information relating to a node (i.e.
QString identifier
A string identifying the node.
QgsStyleEntityVisitorInterface::NodeType type
Node type.
Contains information relating to the style entity currently being visited.
const QgsStyleEntityInterface * entity
Reference to style entity being visited.
QString identifier
A string identifying the style entity.