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