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