QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
qgssvgcache.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssvgcache.h
3  ------------------------------
4  begin : 2011
5  copyright : (C) 2011 by Marco Hugentobler
6  email : marco dot hugentobler at sourcepole dot ch
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgssvgcache.h"
19 #include "qgis.h"
20 #include "qgslogger.h"
22 #include "qgsmessagelog.h"
23 #include "qgssymbollayerutils.h"
25 
26 #include <QApplication>
27 #include <QCoreApplication>
28 #include <QCursor>
29 #include <QDomDocument>
30 #include <QDomElement>
31 #include <QFile>
32 #include <QImage>
33 #include <QPainter>
34 #include <QPicture>
35 #include <QSvgRenderer>
36 #include <QFileInfo>
37 #include <QNetworkReply>
38 #include <QNetworkRequest>
39 
41 
42 //
43 // QgsSvgCacheEntry
44 //
45 
46 QgsSvgCacheEntry::QgsSvgCacheEntry( const QString &path, double size, double strokeWidth, double widthScaleFactor, const QColor &fill, const QColor &stroke, double fixedAspectRatio )
48  , size( size )
49  , strokeWidth( strokeWidth )
50  , widthScaleFactor( widthScaleFactor )
51  , fixedAspectRatio( fixedAspectRatio )
52  , fill( fill )
53  , stroke( stroke )
54 {
55 }
56 
57 bool QgsSvgCacheEntry::isEqual( const QgsAbstractContentCacheEntry *other ) const
58 {
59  const QgsSvgCacheEntry *otherSvg = dynamic_cast< const QgsSvgCacheEntry * >( other );
60  // cheapest checks first!
61  if ( !otherSvg
62  || !qgsDoubleNear( otherSvg->fixedAspectRatio, fixedAspectRatio )
63  || !qgsDoubleNear( otherSvg->size, size )
64  || !qgsDoubleNear( otherSvg->strokeWidth, strokeWidth )
65  || !qgsDoubleNear( otherSvg->widthScaleFactor, widthScaleFactor )
66  || otherSvg->fill != fill
67  || otherSvg->stroke != stroke
68  || otherSvg->path != path )
69  return false;
70 
71  return true;
72 }
73 
74 int QgsSvgCacheEntry::dataSize() const
75 {
76  int size = svgContent.size();
77  if ( picture )
78  {
79  size += picture->size();
80  }
81  if ( image )
82  {
83  size += ( image->width() * image->height() * 32 );
84  }
85  return size;
86 }
87 
88 void QgsSvgCacheEntry::dump() const
89 {
90  QgsDebugMsgLevel( QStringLiteral( "path: %1, size %2, width scale factor %3" ).arg( path ).arg( size ).arg( widthScaleFactor ), 4 );
91 }
93 
94 
95 //
96 // QgsSvgCache
97 //
98 
99 QgsSvgCache::QgsSvgCache( QObject *parent )
100  : QgsAbstractContentCache< QgsSvgCacheEntry >( parent, QObject::tr( "SVG" ) )
101 {
102  mMissingSvg = QStringLiteral( "<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
103 
104  const QString downloadingSvgPath = QgsApplication::defaultThemePath() + QStringLiteral( "downloading_svg.svg" );
105  if ( QFile::exists( downloadingSvgPath ) )
106  {
107  QFile file( downloadingSvgPath );
108  if ( file.open( QIODevice::ReadOnly ) )
109  {
110  mFetchingSvg = file.readAll();
111  }
112  }
113 
114  if ( mFetchingSvg.isEmpty() )
115  {
116  mFetchingSvg = QStringLiteral( "<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
117  }
118 
120 }
121 
122 QImage QgsSvgCache::svgAsImage( const QString &file, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
123  double widthScaleFactor, bool &fitsInCache, double fixedAspectRatio, bool blocking )
124 {
125  QMutexLocker locker( &mMutex );
126 
127  fitsInCache = true;
128  QgsSvgCacheEntry *currentEntry = cacheEntry( file, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking );
129 
130  QImage result;
131 
132  //if current entry image is 0: cache image for entry
133  // checks to see if image will fit into cache
134  //update stats for memory usage
135  if ( !currentEntry->image )
136  {
137  QSvgRenderer r( currentEntry->svgContent );
138  double hwRatio = 1.0;
139  if ( r.viewBoxF().width() > 0 )
140  {
141  if ( currentEntry->fixedAspectRatio > 0 )
142  {
143  hwRatio = currentEntry->fixedAspectRatio;
144  }
145  else
146  {
147  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
148  }
149  }
150  long cachedDataSize = 0;
151  cachedDataSize += currentEntry->svgContent.size();
152  cachedDataSize += static_cast< int >( currentEntry->size * currentEntry->size * hwRatio * 32 );
153  if ( cachedDataSize > mMaxCacheSize / 2 )
154  {
155  fitsInCache = false;
156  currentEntry->image.reset();
157 
158  // instead cache picture
159  if ( !currentEntry->picture )
160  {
161  cachePicture( currentEntry, false );
162  }
163 
164  // ...and render cached picture to result image
165  result = imageFromCachedPicture( *currentEntry );
166  }
167  else
168  {
169  cacheImage( currentEntry );
170  result = *( currentEntry->image );
171  }
173  }
174  else
175  {
176  result = *( currentEntry->image );
177  }
178 
179  return result;
180 }
181 
182 QPicture QgsSvgCache::svgAsPicture( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
183  double widthScaleFactor, bool forceVectorOutput, double fixedAspectRatio, bool blocking )
184 {
185  QMutexLocker locker( &mMutex );
186 
187  QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking );
188 
189  //if current entry picture is 0: cache picture for entry
190  //update stats for memory usage
191  if ( !currentEntry->picture )
192  {
193  cachePicture( currentEntry, forceVectorOutput );
195  }
196 
197  QPicture p;
198  // For some reason p.detach() doesn't seem to always work as intended, at
199  // least with QT 5.5 on Ubuntu 16.04
200  // Serialization/deserialization is a safe way to be ensured we don't
201  // share a copy.
202  p.setData( currentEntry->picture->data(), currentEntry->picture->size() );
203  return p;
204 }
205 
206 QByteArray QgsSvgCache::svgContent( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
207  double widthScaleFactor, double fixedAspectRatio, bool blocking, bool *isMissingImage )
208 {
209  QMutexLocker locker( &mMutex );
210 
211  QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking, isMissingImage );
212 
213  return currentEntry->svgContent;
214 }
215 
216 QSizeF QgsSvgCache::svgViewboxSize( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
217  double widthScaleFactor, double fixedAspectRatio, bool blocking )
218 {
219  QMutexLocker locker( &mMutex );
220 
221  QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking );
222  return currentEntry->viewboxSize;
223 }
224 
225 void QgsSvgCache::containsParams( const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasStrokeParam, QColor &defaultStrokeColor,
226  bool &hasStrokeWidthParam, double &defaultStrokeWidth, bool blocking ) const
227 {
228  bool hasDefaultFillColor = false;
229  bool hasFillOpacityParam = false;
230  bool hasDefaultFillOpacity = false;
231  double defaultFillOpacity = 1.0;
232  bool hasDefaultStrokeColor = false;
233  bool hasDefaultStrokeWidth = false;
234  bool hasStrokeOpacityParam = false;
235  bool hasDefaultStrokeOpacity = false;
236  double defaultStrokeOpacity = 1.0;
237 
238  containsParams( path, hasFillParam, hasDefaultFillColor, defaultFillColor,
239  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
240  hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
241  hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
242  hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity,
243  blocking );
244 }
245 
246 void QgsSvgCache::containsParams( const QString &path,
247  bool &hasFillParam, bool &hasDefaultFillParam, QColor &defaultFillColor,
248  bool &hasFillOpacityParam, bool &hasDefaultFillOpacity, double &defaultFillOpacity,
249  bool &hasStrokeParam, bool &hasDefaultStrokeColor, QColor &defaultStrokeColor,
250  bool &hasStrokeWidthParam, bool &hasDefaultStrokeWidth, double &defaultStrokeWidth,
251  bool &hasStrokeOpacityParam, bool &hasDefaultStrokeOpacity, double &defaultStrokeOpacity,
252  bool blocking ) const
253 {
254  hasFillParam = false;
255  hasFillOpacityParam = false;
256  hasStrokeParam = false;
257  hasStrokeWidthParam = false;
258  hasStrokeOpacityParam = false;
259  defaultFillColor = QColor( Qt::white );
260  defaultFillOpacity = 1.0;
261  defaultStrokeColor = QColor( Qt::black );
262  defaultStrokeWidth = 0.2;
263  defaultStrokeOpacity = 1.0;
264 
265  hasDefaultFillParam = false;
266  hasDefaultFillOpacity = false;
267  hasDefaultStrokeColor = false;
268  hasDefaultStrokeWidth = false;
269  hasDefaultStrokeOpacity = false;
270 
271  QDomDocument svgDoc;
272  if ( !svgDoc.setContent( getContent( path, mMissingSvg, mFetchingSvg, blocking ) ) )
273  {
274  return;
275  }
276 
277  QDomElement docElem = svgDoc.documentElement();
278  containsElemParams( docElem, hasFillParam, hasDefaultFillParam, defaultFillColor,
279  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
280  hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
281  hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
282  hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
283 }
284 
285 void QgsSvgCache::replaceParamsAndCacheSvg( QgsSvgCacheEntry *entry, bool blocking )
286 {
287  if ( !entry )
288  {
289  return;
290  }
291 
292  const QByteArray content = getContent( entry->path, mMissingSvg, mFetchingSvg, blocking ) ;
293  entry->isMissingImage = content == mMissingSvg;
294  QDomDocument svgDoc;
295  if ( !svgDoc.setContent( content ) )
296  {
297  return;
298  }
299 
300  //replace fill color, stroke color, stroke with in all nodes
301  QDomElement docElem = svgDoc.documentElement();
302 
303  QSizeF viewboxSize;
304  double sizeScaleFactor = calcSizeScaleFactor( entry, docElem, viewboxSize );
305  entry->viewboxSize = viewboxSize;
306  replaceElemParams( docElem, entry->fill, entry->stroke, entry->strokeWidth * sizeScaleFactor );
307 
308  entry->svgContent = svgDoc.toByteArray( 0 );
309 
310 
311  // toByteArray screws up tspans inside text by adding new lines before and after each span... this should help, at the
312  // risk of potentially breaking some svgs where the newline is desired
313  entry->svgContent.replace( "\n<tspan", "<tspan" );
314  entry->svgContent.replace( "</tspan>\n", "</tspan>" );
315 
316  mTotalSize += entry->svgContent.size();
317 }
318 
319 double QgsSvgCache::calcSizeScaleFactor( QgsSvgCacheEntry *entry, const QDomElement &docElem, QSizeF &viewboxSize ) const
320 {
321  QString viewBox;
322 
323  //bad size
324  if ( !entry || qgsDoubleNear( entry->size, 0.0 ) )
325  return 1.0;
326 
327  //find svg viewbox attribute
328  //first check if docElem is svg element
329  if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "viewBox" ) ) )
330  {
331  viewBox = docElem.attribute( QStringLiteral( "viewBox" ), QString() );
332  }
333  else if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "viewbox" ) ) )
334  {
335  viewBox = docElem.attribute( QStringLiteral( "viewbox" ), QString() );
336  }
337  else
338  {
339  QDomElement svgElem = docElem.firstChildElement( QStringLiteral( "svg" ) );
340  if ( !svgElem.isNull() )
341  {
342  if ( svgElem.hasAttribute( QStringLiteral( "viewBox" ) ) )
343  viewBox = svgElem.attribute( QStringLiteral( "viewBox" ), QString() );
344  else if ( svgElem.hasAttribute( QStringLiteral( "viewbox" ) ) )
345  viewBox = svgElem.attribute( QStringLiteral( "viewbox" ), QString() );
346  }
347  }
348 
349  //could not find valid viewbox attribute
350  if ( viewBox.isEmpty() )
351  {
352  // trying looking for width/height and use them as a fallback
353  if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "width" ) ) )
354  {
355  const QString widthString = docElem.attribute( QStringLiteral( "width" ) );
356  const QRegularExpression measureRegEx( QStringLiteral( "([\\d\\.]+).*?$" ) );
357  const QRegularExpressionMatch widthMatch = measureRegEx.match( widthString );
358  if ( widthMatch.hasMatch() )
359  {
360  double width = widthMatch.captured( 1 ).toDouble();
361  const QString heightString = docElem.attribute( QStringLiteral( "height" ) );
362 
363  const QRegularExpressionMatch heightMatch = measureRegEx.match( heightString );
364  if ( heightMatch.hasMatch() )
365  {
366  double height = heightMatch.captured( 1 ).toDouble();
367  viewboxSize = QSizeF( width, height );
368  return width / entry->size;
369  }
370  }
371  }
372 
373  return 1.0;
374  }
375 
376 
377  //width should be 3rd element in a 4 part space delimited string
378  QStringList parts = viewBox.split( ' ' );
379  if ( parts.count() != 4 )
380  return 1.0;
381 
382  bool heightOk = false;
383  double height = parts.at( 3 ).toDouble( &heightOk );
384 
385  bool widthOk = false;
386  double width = parts.at( 2 ).toDouble( &widthOk );
387  if ( widthOk )
388  {
389  if ( heightOk )
390  viewboxSize = QSizeF( width, height );
391  return width / entry->size;
392  }
393 
394  return 1.0;
395 }
396 
397 
398 QByteArray QgsSvgCache::getImageData( const QString &path, bool blocking ) const
399 {
400  return getContent( path, mMissingSvg, mFetchingSvg, blocking );
401 };
402 
403 bool QgsSvgCache::checkReply( QNetworkReply *reply, const QString &path ) const
404 {
405  // we accept both real SVG mime types AND plain text types - because some sites
406  // (notably github) serve up svgs as raw text
407  QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
408  if ( !contentType.startsWith( QLatin1String( "image/svg+xml" ), Qt::CaseInsensitive )
409  && !contentType.startsWith( QLatin1String( "text/plain" ), Qt::CaseInsensitive ) )
410  {
411  QgsMessageLog::logMessage( tr( "Unexpected MIME type %1 received for %2" ).arg( contentType, path ), tr( "SVG" ) );
412  return false;
413  }
414  return true;
415 }
416 
417 void QgsSvgCache::cacheImage( QgsSvgCacheEntry *entry )
418 {
419  if ( !entry )
420  {
421  return;
422  }
423 
424  entry->image.reset();
425 
426  QSizeF viewBoxSize;
427  QSizeF scaledSize;
428  QSize imageSize = sizeForImage( *entry, viewBoxSize, scaledSize );
429 
430  // cast double image sizes to int for QImage
431  std::unique_ptr< QImage > image = qgis::make_unique< QImage >( imageSize, QImage::Format_ARGB32_Premultiplied );
432  image->fill( 0 ); // transparent background
433 
434  const bool isFixedAR = entry->fixedAspectRatio > 0;
435 
436  QPainter p( image.get() );
437  QSvgRenderer r( entry->svgContent );
438  if ( qgsDoubleNear( viewBoxSize.width(), viewBoxSize.height() ) )
439  {
440  r.render( &p );
441  }
442  else
443  {
444  QSizeF s( viewBoxSize );
445  s.scale( scaledSize.width(), scaledSize.height(), isFixedAR ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio );
446  QRectF rect( ( imageSize.width() - s.width() ) / 2, ( imageSize.height() - s.height() ) / 2, s.width(), s.height() );
447  r.render( &p, rect );
448  }
449 
450  mTotalSize += ( image->width() * image->height() * 32 );
451  entry->image = std::move( image );
452 }
453 
454 void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry, bool forceVectorOutput )
455 {
456  Q_UNUSED( forceVectorOutput )
457  if ( !entry )
458  {
459  return;
460  }
461 
462  entry->picture.reset();
463 
464  bool isFixedAR = entry->fixedAspectRatio > 0;
465 
466  //correct QPictures dpi correction
467  std::unique_ptr< QPicture > picture = qgis::make_unique< QPicture >();
468  QRectF rect;
469  QSvgRenderer r( entry->svgContent );
470  double hwRatio = 1.0;
471  if ( r.viewBoxF().width() > 0 )
472  {
473  if ( isFixedAR )
474  {
475  hwRatio = entry->fixedAspectRatio;
476  }
477  else
478  {
479  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
480  }
481  }
482 
483  double wSize = entry->size;
484  double hSize = wSize * hwRatio;
485 
486  QSizeF s( r.viewBoxF().size() );
487  s.scale( wSize, hSize, isFixedAR ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio );
488  rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
489 
490  QPainter p( picture.get() );
491  r.render( &p, rect );
492  entry->picture = std::move( picture );
493  mTotalSize += entry->picture->size();
494 }
495 
496 QgsSvgCacheEntry *QgsSvgCache::cacheEntry( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
497  double widthScaleFactor, double fixedAspectRatio, bool blocking, bool *isMissingImage )
498 {
499  QgsSvgCacheEntry *currentEntry = findExistingEntry( new QgsSvgCacheEntry( path, size, strokeWidth, widthScaleFactor, fill, stroke, fixedAspectRatio ) );
500 
501  if ( currentEntry->svgContent.isEmpty() )
502  {
503  replaceParamsAndCacheSvg( currentEntry, blocking );
504  }
505 
506  if ( isMissingImage )
507  *isMissingImage = currentEntry->isMissingImage;
508 
509  return currentEntry;
510 }
511 
512 
513 void QgsSvgCache::replaceElemParams( QDomElement &elem, const QColor &fill, const QColor &stroke, double strokeWidth )
514 {
515  if ( elem.isNull() )
516  {
517  return;
518  }
519 
520  //go through attributes
521  QDomNamedNodeMap attributes = elem.attributes();
522  int nAttributes = attributes.count();
523  for ( int i = 0; i < nAttributes; ++i )
524  {
525  QDomAttr attribute = attributes.item( i ).toAttr();
526  //e.g. style="fill:param(fill);param(stroke)"
527  if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
528  {
529  //entries separated by ';'
530  QString newAttributeString;
531 
532  QStringList entryList = attribute.value().split( ';' );
533  QStringList::const_iterator entryIt = entryList.constBegin();
534  for ( ; entryIt != entryList.constEnd(); ++entryIt )
535  {
536  QStringList keyValueSplit = entryIt->split( ':' );
537  if ( keyValueSplit.size() < 2 )
538  {
539  continue;
540  }
541  const QString key = keyValueSplit.at( 0 );
542  QString value = keyValueSplit.at( 1 );
543  QString newValue = value;
544  value = value.trimmed().toLower();
545 
546  if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
547  {
548  newValue = fill.name();
549  }
550  else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
551  {
552  newValue = QString::number( fill.alphaF() );
553  }
554  else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
555  {
556  newValue = stroke.name();
557  }
558  else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
559  {
560  newValue = QString::number( stroke.alphaF() );
561  }
562  else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
563  {
564  newValue = QString::number( strokeWidth );
565  }
566 
567  if ( entryIt != entryList.constBegin() )
568  {
569  newAttributeString.append( ';' );
570  }
571  newAttributeString.append( key + ':' + newValue );
572  }
573  elem.setAttribute( attribute.name(), newAttributeString );
574  }
575  else
576  {
577  const QString value = attribute.value().trimmed().toLower();
578  if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
579  {
580  elem.setAttribute( attribute.name(), fill.name() );
581  }
582  else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
583  {
584  elem.setAttribute( attribute.name(), fill.alphaF() );
585  }
586  else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
587  {
588  elem.setAttribute( attribute.name(), stroke.name() );
589  }
590  else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
591  {
592  elem.setAttribute( attribute.name(), stroke.alphaF() );
593  }
594  else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
595  {
596  elem.setAttribute( attribute.name(), QString::number( strokeWidth ) );
597  }
598  }
599  }
600 
601  QDomNodeList childList = elem.childNodes();
602  int nChildren = childList.count();
603  for ( int i = 0; i < nChildren; ++i )
604  {
605  QDomElement childElem = childList.at( i ).toElement();
606  replaceElemParams( childElem, fill, stroke, strokeWidth );
607  }
608 }
609 
610 void QgsSvgCache::containsElemParams( const QDomElement &elem, bool &hasFillParam, bool &hasDefaultFill, QColor &defaultFill,
611  bool &hasFillOpacityParam, bool &hasDefaultFillOpacity, double &defaultFillOpacity,
612  bool &hasStrokeParam, bool &hasDefaultStroke, QColor &defaultStroke,
613  bool &hasStrokeWidthParam, bool &hasDefaultStrokeWidth, double &defaultStrokeWidth,
614  bool &hasStrokeOpacityParam, bool &hasDefaultStrokeOpacity, double &defaultStrokeOpacity ) const
615 {
616  if ( elem.isNull() )
617  {
618  return;
619  }
620 
621  //we already have all the information, no need to go deeper
622  if ( hasFillParam && hasStrokeParam && hasStrokeWidthParam && hasFillOpacityParam && hasStrokeOpacityParam )
623  {
624  return;
625  }
626 
627  //check this elements attribute
628  QDomNamedNodeMap attributes = elem.attributes();
629  int nAttributes = attributes.count();
630 
631  QStringList valueSplit;
632  for ( int i = 0; i < nAttributes; ++i )
633  {
634  QDomAttr attribute = attributes.item( i ).toAttr();
635  if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
636  {
637  //entries separated by ';'
638  QStringList entryList = attribute.value().split( ';' );
639  QStringList::const_iterator entryIt = entryList.constBegin();
640  for ( ; entryIt != entryList.constEnd(); ++entryIt )
641  {
642  QStringList keyValueSplit = entryIt->split( ':' );
643  if ( keyValueSplit.size() < 2 )
644  {
645  continue;
646  }
647  QString value = keyValueSplit.at( 1 );
648  valueSplit = value.split( ' ' );
649  if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
650  {
651  hasFillParam = true;
652  if ( valueSplit.size() > 1 )
653  {
654  defaultFill = QColor( valueSplit.at( 1 ) );
655  hasDefaultFill = true;
656  }
657  }
658  else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
659  {
660  hasFillOpacityParam = true;
661  if ( valueSplit.size() > 1 )
662  {
663  bool ok;
664  double opacity = valueSplit.at( 1 ).toDouble( &ok );
665  if ( ok )
666  {
667  defaultFillOpacity = opacity;
668  hasDefaultFillOpacity = true;
669  }
670  }
671  }
672  else if ( !hasStrokeParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
673  {
674  hasStrokeParam = true;
675  if ( valueSplit.size() > 1 )
676  {
677  defaultStroke = QColor( valueSplit.at( 1 ) );
678  hasDefaultStroke = true;
679  }
680  }
681  else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
682  {
683  hasStrokeWidthParam = true;
684  if ( valueSplit.size() > 1 )
685  {
686  defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
687  hasDefaultStrokeWidth = true;
688  }
689  }
690  else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
691  {
692  hasStrokeOpacityParam = true;
693  if ( valueSplit.size() > 1 )
694  {
695  bool ok;
696  double opacity = valueSplit.at( 1 ).toDouble( &ok );
697  if ( ok )
698  {
699  defaultStrokeOpacity = opacity;
700  hasDefaultStrokeOpacity = true;
701  }
702  }
703  }
704  }
705  }
706  else
707  {
708  QString value = attribute.value();
709  valueSplit = value.split( ' ' );
710  if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
711  {
712  hasFillParam = true;
713  if ( valueSplit.size() > 1 )
714  {
715  defaultFill = QColor( valueSplit.at( 1 ) );
716  hasDefaultFill = true;
717  }
718  }
719  else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
720  {
721  hasFillOpacityParam = true;
722  if ( valueSplit.size() > 1 )
723  {
724  bool ok;
725  double opacity = valueSplit.at( 1 ).toDouble( &ok );
726  if ( ok )
727  {
728  defaultFillOpacity = opacity;
729  hasDefaultFillOpacity = true;
730  }
731  }
732  }
733  else if ( !hasStrokeParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
734  {
735  hasStrokeParam = true;
736  if ( valueSplit.size() > 1 )
737  {
738  defaultStroke = QColor( valueSplit.at( 1 ) );
739  hasDefaultStroke = true;
740  }
741  }
742  else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
743  {
744  hasStrokeWidthParam = true;
745  if ( valueSplit.size() > 1 )
746  {
747  defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
748  hasDefaultStrokeWidth = true;
749  }
750  }
751  else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
752  {
753  hasStrokeOpacityParam = true;
754  if ( valueSplit.size() > 1 )
755  {
756  bool ok;
757  double opacity = valueSplit.at( 1 ).toDouble( &ok );
758  if ( ok )
759  {
760  defaultStrokeOpacity = opacity;
761  hasDefaultStrokeOpacity = true;
762  }
763  }
764  }
765  }
766  }
767 
768  //pass it further to child items
769  QDomNodeList childList = elem.childNodes();
770  int nChildren = childList.count();
771  for ( int i = 0; i < nChildren; ++i )
772  {
773  QDomElement childElem = childList.at( i ).toElement();
774  containsElemParams( childElem, hasFillParam, hasDefaultFill, defaultFill,
775  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
776  hasStrokeParam, hasDefaultStroke, defaultStroke,
777  hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
778  hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
779  }
780 }
781 
782 QSize QgsSvgCache::sizeForImage( const QgsSvgCacheEntry &entry, QSizeF &viewBoxSize, QSizeF &scaledSize ) const
783 {
784  bool isFixedAR = entry.fixedAspectRatio > 0;
785 
786  QSvgRenderer r( entry.svgContent );
787  double hwRatio = 1.0;
788  viewBoxSize = r.viewBoxF().size();
789  if ( viewBoxSize.width() > 0 )
790  {
791  if ( isFixedAR )
792  {
793  hwRatio = entry.fixedAspectRatio;
794  }
795  else
796  {
797  hwRatio = viewBoxSize.height() / viewBoxSize.width();
798  }
799  }
800 
801  // cast double image sizes to int for QImage
802  scaledSize.setWidth( entry.size );
803  int wImgSize = static_cast< int >( scaledSize.width() );
804  if ( wImgSize < 1 )
805  {
806  wImgSize = 1;
807  }
808  scaledSize.setHeight( scaledSize.width() * hwRatio );
809  int hImgSize = static_cast< int >( scaledSize.height() );
810  if ( hImgSize < 1 )
811  {
812  hImgSize = 1;
813  }
814  return QSize( wImgSize, hImgSize );
815 }
816 
817 QImage QgsSvgCache::imageFromCachedPicture( const QgsSvgCacheEntry &entry ) const
818 {
819  QSizeF viewBoxSize;
820  QSizeF scaledSize;
821  QImage image( sizeForImage( entry, viewBoxSize, scaledSize ), QImage::Format_ARGB32_Premultiplied );
822  image.fill( 0 ); // transparent background
823 
824  QPainter p( &image );
825  p.drawPicture( QPoint( 0, 0 ), *entry.picture );
826  return image;
827 }
828 
qgssvgcache.h
QgsDebugMsgLevel
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QgsSvgCache::checkReply
bool checkReply(QNetworkReply *reply, const QString &path) const override
Runs additional checks on a network reply to ensure that the reply content is consistent with that re...
Definition: qgssvgcache.cpp:403
qgssymbollayerutils.h
QgsSvgCache::svgAsImage
QImage svgAsImage(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool &fitsInCache, double fixedAspectRatio=0, bool blocking=false)
Gets SVG as QImage.
Definition: qgssvgcache.cpp:122
qgis.h
qgsnetworkcontentfetchertask.h
QgsSvgCache::containsParams
void containsParams(const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasStrokeParam, QColor &defaultStrokeColor, bool &hasStrokeWidthParam, double &defaultStrokeWidth, bool blocking=false) const
Tests if an svg file contains parameters for fill, stroke color, stroke width.
Definition: qgssvgcache.cpp:225
QgsSvgCache::QgsSvgCache
QgsSvgCache(QObject *parent=nullptr)
Constructor for QgsSvgCache.
Definition: qgssvgcache.cpp:99
QgsSvgCache::svgViewboxSize
QSizeF svgViewboxSize(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0, bool blocking=false)
Calculates the viewbox size of a (possibly cached) SVG file.
Definition: qgssvgcache.cpp:216
qgsDoubleNear
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
qgsnetworkaccessmanager.h
QgsSvgCache::getImageData
QByteArray getImageData(const QString &path, bool blocking=false) const
Gets the SVG content corresponding to the given path.
Definition: qgssvgcache.cpp:398
QgsAbstractContentCacheEntry
Base class for entries in a QgsAbstractContentCache.
Definition: qgsabstractcontentcache.h:48
QgsAbstractContentCache< QgsSvgCacheEntry >::findExistingEntry
QgsSvgCacheEntry * findExistingEntry(QgsSvgCacheEntry *entryTemplate)
Returns the existing entry from the cache which matches entryTemplate (deleting entryTemplate when do...
Definition: qgsabstractcontentcache.h:492
QgsMessageLog::logMessage
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Definition: qgsmessagelog.cpp:27
QgsAbstractContentCacheBase::remoteContentFetched
void remoteContentFetched(const QString &url)
Emitted when the cache has finished retrieving content from a remote url.
QgsSvgCache::svgAsPicture
QPicture svgAsPicture(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool forceVectorOutput=false, double fixedAspectRatio=0, bool blocking=false)
Gets SVG as QPicture&.
Definition: qgssvgcache.cpp:182
qgslogger.h
QgsAbstractContentCache< QgsSvgCacheEntry >::trimToMaximumSize
void trimToMaximumSize()
Removes the least used cache entries until the maximum cache size is under the predefined size limit.
Definition: qgsabstractcontentcache.h:227
QgsSvgCache::svgContent
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0, bool blocking=false, bool *isMissingImage=nullptr)
Gets SVG content.
Definition: qgssvgcache.cpp:206
QgsAbstractContentCache
Abstract base class for file content caches, such as SVG or raster image caches.
Definition: qgsabstractcontentcache.h:190
QgsApplication::defaultThemePath
static QString defaultThemePath()
Returns the path to the default theme directory.
Definition: qgsapplication.cpp:586
QgsAbstractContentCache< QgsSvgCacheEntry >::mMutex
QMutex mMutex
Definition: qgsabstractcontentcache.h:549
QgsAbstractContentCache< QgsSvgCacheEntry >::mTotalSize
long mTotalSize
Estimated total size of all cached content.
Definition: qgsabstractcontentcache.h:551
qgsmessagelog.h
QgsAbstractContentCache< QgsSvgCacheEntry >::getContent
QByteArray getContent(const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking=false) const
Gets the file content corresponding to the given path.
Definition: qgsabstractcontentcache.h:260
QgsSvgCache::remoteSvgFetched
void remoteSvgFetched(const QString &url)
Emitted when the cache has finished retrieving an SVG file from a remote url.
QgsAbstractContentCache< QgsSvgCacheEntry >::mMaxCacheSize
long mMaxCacheSize
Maximum cache size.
Definition: qgsabstractcontentcache.h:554