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