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