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