QGIS API Documentation  3.14.0-Pi (9f7028fd23)
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  return 1.0;
352 
353  //width should be 3rd element in a 4 part space delimited string
354  QStringList parts = viewBox.split( ' ' );
355  if ( parts.count() != 4 )
356  return 1.0;
357 
358  bool heightOk = false;
359  double height = parts.at( 3 ).toDouble( &heightOk );
360 
361  bool widthOk = false;
362  double width = parts.at( 2 ).toDouble( &widthOk );
363  if ( widthOk )
364  {
365  if ( heightOk )
366  viewboxSize = QSizeF( width, height );
367  return width / entry->size;
368  }
369 
370  return 1.0;
371 }
372 
373 
374 QByteArray QgsSvgCache::getImageData( const QString &path, bool blocking ) const
375 {
376  return getContent( path, mMissingSvg, mFetchingSvg, blocking );
377 };
378 
379 bool QgsSvgCache::checkReply( QNetworkReply *reply, const QString &path ) const
380 {
381  // we accept both real SVG mime types AND plain text types - because some sites
382  // (notably github) serve up svgs as raw text
383  QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
384  if ( !contentType.startsWith( QLatin1String( "image/svg+xml" ), Qt::CaseInsensitive )
385  && !contentType.startsWith( QLatin1String( "text/plain" ), Qt::CaseInsensitive ) )
386  {
387  QgsMessageLog::logMessage( tr( "Unexpected MIME type %1 received for %2" ).arg( contentType, path ), tr( "SVG" ) );
388  return false;
389  }
390  return true;
391 }
392 
393 void QgsSvgCache::cacheImage( QgsSvgCacheEntry *entry )
394 {
395  if ( !entry )
396  {
397  return;
398  }
399 
400  entry->image.reset();
401 
402  QSizeF viewBoxSize;
403  QSizeF scaledSize;
404  QSize imageSize = sizeForImage( *entry, viewBoxSize, scaledSize );
405 
406  // cast double image sizes to int for QImage
407  std::unique_ptr< QImage > image = qgis::make_unique< QImage >( imageSize, QImage::Format_ARGB32_Premultiplied );
408  image->fill( 0 ); // transparent background
409 
410  QPainter p( image.get() );
411  QSvgRenderer r( entry->svgContent );
412  if ( qgsDoubleNear( viewBoxSize.width(), viewBoxSize.height() ) )
413  {
414  r.render( &p );
415  }
416  else
417  {
418  QSizeF s( viewBoxSize );
419  s.scale( scaledSize.width(), scaledSize.height(), Qt::KeepAspectRatio );
420  QRectF rect( ( imageSize.width() - s.width() ) / 2, ( imageSize.height() - s.height() ) / 2, s.width(), s.height() );
421  r.render( &p, rect );
422  }
423 
424  mTotalSize += ( image->width() * image->height() * 32 );
425  entry->image = std::move( image );
426 }
427 
428 void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry, bool forceVectorOutput )
429 {
430  Q_UNUSED( forceVectorOutput )
431  if ( !entry )
432  {
433  return;
434  }
435 
436  entry->picture.reset();
437 
438  bool isFixedAR = entry->fixedAspectRatio > 0;
439 
440  //correct QPictures dpi correction
441  std::unique_ptr< QPicture > picture = qgis::make_unique< QPicture >();
442  QRectF rect;
443  QSvgRenderer r( entry->svgContent );
444  double hwRatio = 1.0;
445  if ( r.viewBoxF().width() > 0 )
446  {
447  if ( isFixedAR )
448  {
449  hwRatio = entry->fixedAspectRatio;
450  }
451  else
452  {
453  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
454  }
455  }
456 
457  double wSize = entry->size;
458  double hSize = wSize * hwRatio;
459 
460  QSizeF s( r.viewBoxF().size() );
461  s.scale( wSize, hSize, isFixedAR ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio );
462  rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
463 
464  QPainter p( picture.get() );
465  r.render( &p, rect );
466  entry->picture = std::move( picture );
467  mTotalSize += entry->picture->size();
468 }
469 
470 QgsSvgCacheEntry *QgsSvgCache::cacheEntry( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
471  double widthScaleFactor, double fixedAspectRatio, bool blocking, bool *isMissingImage )
472 {
473  QgsSvgCacheEntry *currentEntry = findExistingEntry( new QgsSvgCacheEntry( path, size, strokeWidth, widthScaleFactor, fill, stroke, fixedAspectRatio ) );
474 
475  if ( currentEntry->svgContent.isEmpty() )
476  {
477  replaceParamsAndCacheSvg( currentEntry, blocking );
478  }
479 
480  if ( isMissingImage )
481  *isMissingImage = currentEntry->isMissingImage;
482 
483  return currentEntry;
484 }
485 
486 
487 void QgsSvgCache::replaceElemParams( QDomElement &elem, const QColor &fill, const QColor &stroke, double strokeWidth )
488 {
489  if ( elem.isNull() )
490  {
491  return;
492  }
493 
494  //go through attributes
495  QDomNamedNodeMap attributes = elem.attributes();
496  int nAttributes = attributes.count();
497  for ( int i = 0; i < nAttributes; ++i )
498  {
499  QDomAttr attribute = attributes.item( i ).toAttr();
500  //e.g. style="fill:param(fill);param(stroke)"
501  if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
502  {
503  //entries separated by ';'
504  QString newAttributeString;
505 
506  QStringList entryList = attribute.value().split( ';' );
507  QStringList::const_iterator entryIt = entryList.constBegin();
508  for ( ; entryIt != entryList.constEnd(); ++entryIt )
509  {
510  QStringList keyValueSplit = entryIt->split( ':' );
511  if ( keyValueSplit.size() < 2 )
512  {
513  continue;
514  }
515  const QString key = keyValueSplit.at( 0 );
516  QString value = keyValueSplit.at( 1 );
517  QString newValue = value;
518  value = value.trimmed().toLower();
519 
520  if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
521  {
522  newValue = fill.name();
523  }
524  else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
525  {
526  newValue = QString::number( fill.alphaF() );
527  }
528  else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
529  {
530  newValue = stroke.name();
531  }
532  else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
533  {
534  newValue = QString::number( stroke.alphaF() );
535  }
536  else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
537  {
538  newValue = QString::number( strokeWidth );
539  }
540 
541  if ( entryIt != entryList.constBegin() )
542  {
543  newAttributeString.append( ';' );
544  }
545  newAttributeString.append( key + ':' + newValue );
546  }
547  elem.setAttribute( attribute.name(), newAttributeString );
548  }
549  else
550  {
551  const QString value = attribute.value().trimmed().toLower();
552  if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
553  {
554  elem.setAttribute( attribute.name(), fill.name() );
555  }
556  else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
557  {
558  elem.setAttribute( attribute.name(), fill.alphaF() );
559  }
560  else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
561  {
562  elem.setAttribute( attribute.name(), stroke.name() );
563  }
564  else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
565  {
566  elem.setAttribute( attribute.name(), stroke.alphaF() );
567  }
568  else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
569  {
570  elem.setAttribute( attribute.name(), QString::number( strokeWidth ) );
571  }
572  }
573  }
574 
575  QDomNodeList childList = elem.childNodes();
576  int nChildren = childList.count();
577  for ( int i = 0; i < nChildren; ++i )
578  {
579  QDomElement childElem = childList.at( i ).toElement();
580  replaceElemParams( childElem, fill, stroke, strokeWidth );
581  }
582 }
583 
584 void QgsSvgCache::containsElemParams( const QDomElement &elem, bool &hasFillParam, bool &hasDefaultFill, QColor &defaultFill,
585  bool &hasFillOpacityParam, bool &hasDefaultFillOpacity, double &defaultFillOpacity,
586  bool &hasStrokeParam, bool &hasDefaultStroke, QColor &defaultStroke,
587  bool &hasStrokeWidthParam, bool &hasDefaultStrokeWidth, double &defaultStrokeWidth,
588  bool &hasStrokeOpacityParam, bool &hasDefaultStrokeOpacity, double &defaultStrokeOpacity ) const
589 {
590  if ( elem.isNull() )
591  {
592  return;
593  }
594 
595  //we already have all the information, no need to go deeper
596  if ( hasFillParam && hasStrokeParam && hasStrokeWidthParam && hasFillOpacityParam && hasStrokeOpacityParam )
597  {
598  return;
599  }
600 
601  //check this elements attribute
602  QDomNamedNodeMap attributes = elem.attributes();
603  int nAttributes = attributes.count();
604 
605  QStringList valueSplit;
606  for ( int i = 0; i < nAttributes; ++i )
607  {
608  QDomAttr attribute = attributes.item( i ).toAttr();
609  if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
610  {
611  //entries separated by ';'
612  QStringList entryList = attribute.value().split( ';' );
613  QStringList::const_iterator entryIt = entryList.constBegin();
614  for ( ; entryIt != entryList.constEnd(); ++entryIt )
615  {
616  QStringList keyValueSplit = entryIt->split( ':' );
617  if ( keyValueSplit.size() < 2 )
618  {
619  continue;
620  }
621  QString value = keyValueSplit.at( 1 );
622  valueSplit = value.split( ' ' );
623  if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
624  {
625  hasFillParam = true;
626  if ( valueSplit.size() > 1 )
627  {
628  defaultFill = QColor( valueSplit.at( 1 ) );
629  hasDefaultFill = true;
630  }
631  }
632  else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
633  {
634  hasFillOpacityParam = true;
635  if ( valueSplit.size() > 1 )
636  {
637  bool ok;
638  double opacity = valueSplit.at( 1 ).toDouble( &ok );
639  if ( ok )
640  {
641  defaultFillOpacity = opacity;
642  hasDefaultFillOpacity = true;
643  }
644  }
645  }
646  else if ( !hasStrokeParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
647  {
648  hasStrokeParam = true;
649  if ( valueSplit.size() > 1 )
650  {
651  defaultStroke = QColor( valueSplit.at( 1 ) );
652  hasDefaultStroke = true;
653  }
654  }
655  else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
656  {
657  hasStrokeWidthParam = true;
658  if ( valueSplit.size() > 1 )
659  {
660  defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
661  hasDefaultStrokeWidth = true;
662  }
663  }
664  else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
665  {
666  hasStrokeOpacityParam = true;
667  if ( valueSplit.size() > 1 )
668  {
669  bool ok;
670  double opacity = valueSplit.at( 1 ).toDouble( &ok );
671  if ( ok )
672  {
673  defaultStrokeOpacity = opacity;
674  hasDefaultStrokeOpacity = true;
675  }
676  }
677  }
678  }
679  }
680  else
681  {
682  QString value = attribute.value();
683  valueSplit = value.split( ' ' );
684  if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
685  {
686  hasFillParam = true;
687  if ( valueSplit.size() > 1 )
688  {
689  defaultFill = QColor( valueSplit.at( 1 ) );
690  hasDefaultFill = true;
691  }
692  }
693  else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
694  {
695  hasFillOpacityParam = true;
696  if ( valueSplit.size() > 1 )
697  {
698  bool ok;
699  double opacity = valueSplit.at( 1 ).toDouble( &ok );
700  if ( ok )
701  {
702  defaultFillOpacity = opacity;
703  hasDefaultFillOpacity = true;
704  }
705  }
706  }
707  else if ( !hasStrokeParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
708  {
709  hasStrokeParam = true;
710  if ( valueSplit.size() > 1 )
711  {
712  defaultStroke = QColor( valueSplit.at( 1 ) );
713  hasDefaultStroke = true;
714  }
715  }
716  else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
717  {
718  hasStrokeWidthParam = true;
719  if ( valueSplit.size() > 1 )
720  {
721  defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
722  hasDefaultStrokeWidth = true;
723  }
724  }
725  else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
726  {
727  hasStrokeOpacityParam = true;
728  if ( valueSplit.size() > 1 )
729  {
730  bool ok;
731  double opacity = valueSplit.at( 1 ).toDouble( &ok );
732  if ( ok )
733  {
734  defaultStrokeOpacity = opacity;
735  hasDefaultStrokeOpacity = true;
736  }
737  }
738  }
739  }
740  }
741 
742  //pass it further to child items
743  QDomNodeList childList = elem.childNodes();
744  int nChildren = childList.count();
745  for ( int i = 0; i < nChildren; ++i )
746  {
747  QDomElement childElem = childList.at( i ).toElement();
748  containsElemParams( childElem, hasFillParam, hasDefaultFill, defaultFill,
749  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
750  hasStrokeParam, hasDefaultStroke, defaultStroke,
751  hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
752  hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
753  }
754 }
755 
756 QSize QgsSvgCache::sizeForImage( const QgsSvgCacheEntry &entry, QSizeF &viewBoxSize, QSizeF &scaledSize ) const
757 {
758  bool isFixedAR = entry.fixedAspectRatio > 0;
759 
760  QSvgRenderer r( entry.svgContent );
761  double hwRatio = 1.0;
762  viewBoxSize = r.viewBoxF().size();
763  if ( viewBoxSize.width() > 0 )
764  {
765  if ( isFixedAR )
766  {
767  hwRatio = entry.fixedAspectRatio;
768  }
769  else
770  {
771  hwRatio = viewBoxSize.height() / viewBoxSize.width();
772  }
773  }
774 
775  // cast double image sizes to int for QImage
776  scaledSize.setWidth( entry.size );
777  int wImgSize = static_cast< int >( scaledSize.width() );
778  if ( wImgSize < 1 )
779  {
780  wImgSize = 1;
781  }
782  scaledSize.setHeight( scaledSize.width() * hwRatio );
783  int hImgSize = static_cast< int >( scaledSize.height() );
784  if ( hImgSize < 1 )
785  {
786  hImgSize = 1;
787  }
788  return QSize( wImgSize, hImgSize );
789 }
790 
791 QImage QgsSvgCache::imageFromCachedPicture( const QgsSvgCacheEntry &entry ) const
792 {
793  QSizeF viewBoxSize;
794  QSizeF scaledSize;
795  QImage image( sizeForImage( entry, viewBoxSize, scaledSize ), QImage::Format_ARGB32_Premultiplied );
796  image.fill( 0 ); // transparent background
797 
798  QPainter p( &image );
799  p.drawPicture( QPoint( 0, 0 ), *entry.picture );
800  return image;
801 }
802 
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:379
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:374
QgsAbstractContentCacheEntry
Definition: qgsabstractcontentcache.h:47
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
Definition: qgsabstractcontentcache.h:189
QgsApplication::defaultThemePath
static QString defaultThemePath()
Returns the path to the default theme directory.
Definition: qgsapplication.cpp:565
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