QGIS API Documentation  2.99.0-Master (c558d51)
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"
24 
25 #include <QApplication>
26 #include <QCoreApplication>
27 #include <QCursor>
28 #include <QDomDocument>
29 #include <QDomElement>
30 #include <QFile>
31 #include <QImage>
32 #include <QPainter>
33 #include <QPicture>
34 #include <QSvgRenderer>
35 #include <QFileInfo>
36 #include <QNetworkReply>
37 #include <QNetworkRequest>
38 
40  : file( QString() )
41  , size( 0.0 )
42  , outlineWidth( 0 )
43  , widthScaleFactor( 1.0 )
44  , rasterScaleFactor( 1.0 )
45  , fill( Qt::black )
46  , outline( Qt::black )
47  , image( nullptr )
48  , picture( nullptr )
49  , nextEntry( nullptr )
50  , previousEntry( nullptr )
51 {
52 }
53 
54 QgsSvgCacheEntry::QgsSvgCacheEntry( const QString& f, double s, double ow, double wsf, double rsf, const QColor& fi, const QColor& ou, const QString& lk )
55  : file( f )
56  , lookupKey( lk.isEmpty() ? f : lk )
57  , size( s )
58  , outlineWidth( ow )
59  , widthScaleFactor( wsf )
60  , rasterScaleFactor( rsf )
61  , fill( fi )
62  , outline( ou )
63  , image( nullptr )
64  , picture( nullptr )
65  , nextEntry( nullptr )
66  , previousEntry( nullptr )
67 {
68 }
69 
70 
72 {
73  delete image;
74  delete picture;
75 }
76 
78 {
80  && qgsDoubleNear( other.rasterScaleFactor, rasterScaleFactor ) && other.fill == fill && other.outline == outline;
81 }
82 
84 {
85  int size = svgContent.size();
86  if ( picture )
87  {
88  size += picture->size();
89  }
90  if ( image )
91  {
92  size += ( image->width() * image->height() * 32 );
93  }
94  return size;
95 }
96 
98 {
99  static QgsSvgCache mInstance;
100  return &mInstance;
101 }
102 
103 QgsSvgCache::QgsSvgCache( QObject *parent )
104  : QObject( parent )
105  , mTotalSize( 0 )
106  , mLeastRecentEntry( nullptr )
107  , mMostRecentEntry( nullptr )
108 {
109  mMissingSvg = QStringLiteral( "<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
110 }
111 
113 {
114  qDeleteAll( mEntryLookup );
115 }
116 
117 
118 const QImage& QgsSvgCache::svgAsImage( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
119  double widthScaleFactor, double rasterScaleFactor, bool& fitsInCache )
120 {
121  QMutexLocker locker( &mMutex );
122 
123  fitsInCache = true;
124  QgsSvgCacheEntry* currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
125 
126  //if current entry image is 0: cache image for entry
127  // checks to see if image will fit into cache
128  //update stats for memory usage
129  if ( !currentEntry->image )
130  {
131  QSvgRenderer r( currentEntry->svgContent );
132  double hwRatio = 1.0;
133  if ( r.viewBoxF().width() > 0 )
134  {
135  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
136  }
137  long cachedDataSize = 0;
138  cachedDataSize += currentEntry->svgContent.size();
139  cachedDataSize += static_cast< int >( currentEntry->size * currentEntry->size * hwRatio * 32 );
140  if ( cachedDataSize > mMaximumSize / 2 )
141  {
142  fitsInCache = false;
143  delete currentEntry->image;
144  currentEntry->image = nullptr;
145  //currentEntry->image = new QImage( 0, 0 );
146 
147  // instead cache picture
148  if ( !currentEntry->picture )
149  {
150  cachePicture( currentEntry, false );
151  }
152  }
153  else
154  {
155  cacheImage( currentEntry );
156  }
158  }
159 
160  return *( currentEntry->image );
161 }
162 
163 const QPicture& QgsSvgCache::svgAsPicture( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
164  double widthScaleFactor, double rasterScaleFactor, bool forceVectorOutput )
165 {
166  QMutexLocker locker( &mMutex );
167 
168  QgsSvgCacheEntry* currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
169 
170  //if current entry picture is 0: cache picture for entry
171  //update stats for memory usage
172  if ( !currentEntry->picture )
173  {
174  cachePicture( currentEntry, forceVectorOutput );
176  }
177 
178  return *( currentEntry->picture );
179 }
180 
181 const QByteArray& QgsSvgCache::svgContent( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
182  double widthScaleFactor, double rasterScaleFactor )
183 {
184  QMutexLocker locker( &mMutex );
185 
186  QgsSvgCacheEntry *currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
187 
188  return currentEntry->svgContent;
189 }
190 
191 QSizeF QgsSvgCache::svgViewboxSize( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor )
192 {
193  QMutexLocker locker( &mMutex );
194 
195  QgsSvgCacheEntry *currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
196 
197  return currentEntry->viewboxSize;
198 }
199 
200 QgsSvgCacheEntry* QgsSvgCache::insertSVG( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
201  double widthScaleFactor, double rasterScaleFactor )
202 {
203  // The file may be relative path (e.g. if path is data defined)
204  QString path = QgsSymbolLayerUtils::symbolNameToPath( file );
205 
206  QgsSvgCacheEntry* entry = new QgsSvgCacheEntry( path, size, outlineWidth, widthScaleFactor, rasterScaleFactor, fill, outline, file );
207 
208  replaceParamsAndCacheSvg( entry );
209 
210  mEntryLookup.insert( file, entry );
211 
212  //insert to most recent place in entry list
213  if ( !mMostRecentEntry ) //inserting first entry
214  {
215  mLeastRecentEntry = entry;
216  mMostRecentEntry = entry;
217  entry->previousEntry = nullptr;
218  entry->nextEntry = nullptr;
219  }
220  else
221  {
222  entry->previousEntry = mMostRecentEntry;
223  entry->nextEntry = nullptr;
224  mMostRecentEntry->nextEntry = entry;
225  mMostRecentEntry = entry;
226  }
227 
229  return entry;
230 }
231 
232 void QgsSvgCache::containsParams( const QString& path, bool& hasFillParam, QColor& defaultFillColor, bool& hasOutlineParam, QColor& defaultOutlineColor,
233  bool& hasOutlineWidthParam, double& defaultOutlineWidth ) const
234 {
235  bool hasDefaultFillColor = false;
236  bool hasFillOpacityParam = false;
237  bool hasDefaultFillOpacity = false;
238  double defaultFillOpacity = 1.0;
239  bool hasDefaultOutlineColor = false;
240  bool hasDefaultOutlineWidth = false;
241  bool hasOutlineOpacityParam = false;
242  bool hasDefaultOutlineOpacity = false;
243  double defaultOutlineOpacity = 1.0;
244 
245  containsParams( path, hasFillParam, hasDefaultFillColor, defaultFillColor,
246  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
247  hasOutlineParam, hasDefaultOutlineColor, defaultOutlineColor,
248  hasOutlineWidthParam, hasDefaultOutlineWidth, defaultOutlineWidth,
249  hasOutlineOpacityParam, hasDefaultOutlineOpacity, defaultOutlineOpacity );
250 }
251 
252 void QgsSvgCache::containsParams( const QString& path,
253  bool& hasFillParam, bool& hasDefaultFillParam, QColor& defaultFillColor,
254  bool& hasFillOpacityParam, bool& hasDefaultFillOpacity, double& defaultFillOpacity,
255  bool& hasOutlineParam, bool& hasDefaultOutlineColor, QColor& defaultOutlineColor,
256  bool& hasOutlineWidthParam, bool& hasDefaultOutlineWidth, double& defaultOutlineWidth,
257  bool& hasOutlineOpacityParam, bool& hasDefaultOutlineOpacity, double& defaultOutlineOpacity ) const
258 {
259  hasFillParam = false;
260  hasFillOpacityParam = false;
261  hasOutlineParam = false;
262  hasOutlineWidthParam = false;
263  hasOutlineOpacityParam = false;
264  defaultFillColor = QColor( Qt::white );
265  defaultFillOpacity = 1.0;
266  defaultOutlineColor = QColor( Qt::black );
267  defaultOutlineWidth = 0.2;
268  defaultOutlineOpacity = 1.0;
269 
270  hasDefaultFillParam = false;
271  hasDefaultFillOpacity = false;
272  hasDefaultOutlineColor = false;
273  hasDefaultOutlineWidth = false;
274  hasDefaultOutlineOpacity = false;
275 
276  QDomDocument svgDoc;
277  if ( !svgDoc.setContent( getImageData( path ) ) )
278  {
279  return;
280  }
281 
282  QDomElement docElem = svgDoc.documentElement();
283  containsElemParams( docElem, hasFillParam, hasDefaultFillParam, defaultFillColor,
284  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
285  hasOutlineParam, hasDefaultOutlineColor, defaultOutlineColor,
286  hasOutlineWidthParam, hasDefaultOutlineWidth, defaultOutlineWidth,
287  hasOutlineOpacityParam, hasDefaultOutlineOpacity, defaultOutlineOpacity );
288 }
289 
291 {
292  if ( !entry )
293  {
294  return;
295  }
296 
297  QDomDocument svgDoc;
298  if ( !svgDoc.setContent( getImageData( entry->file ) ) )
299  {
300  return;
301  }
302 
303  //replace fill color, outline color, outline with in all nodes
304  QDomElement docElem = svgDoc.documentElement();
305 
306  QSizeF viewboxSize;
307  double sizeScaleFactor = calcSizeScaleFactor( entry, docElem, viewboxSize );
308  entry->viewboxSize = viewboxSize;
309  replaceElemParams( docElem, entry->fill, entry->outline, entry->outlineWidth * sizeScaleFactor );
310 
311  entry->svgContent = svgDoc.toByteArray( 0 );
312 
313  // toByteArray screws up tspans inside text by adding new lines before and after each span... this should help, at the
314  // risk of potentially breaking some svgs where the newline is desired
315  entry->svgContent.replace( "\n<tspan", "<tspan" );
316  entry->svgContent.replace( "</tspan>\n", "</tspan>" );
317 
318  mTotalSize += entry->svgContent.size();
319 }
320 
321 double QgsSvgCache::calcSizeScaleFactor( QgsSvgCacheEntry* entry, const QDomElement& docElem, QSizeF& viewboxSize ) const
322 {
323  QString viewBox;
324 
325  //bad size
326  if ( !entry || qgsDoubleNear( entry->size, 0.0 ) )
327  return 1.0;
328 
329  //find svg viewbox attribute
330  //first check if docElem is svg element
331  if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "viewBox" ) ) )
332  {
333  viewBox = docElem.attribute( QStringLiteral( "viewBox" ), QString() );
334  }
335  else if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "viewbox" ) ) )
336  {
337  viewBox = docElem.attribute( QStringLiteral( "viewbox" ), QString() );
338  }
339  else
340  {
341  QDomElement svgElem = docElem.firstChildElement( QStringLiteral( "svg" ) ) ;
342  if ( !svgElem.isNull() )
343  {
344  if ( svgElem.hasAttribute( QStringLiteral( "viewBox" ) ) )
345  viewBox = svgElem.attribute( QStringLiteral( "viewBox" ), QString() );
346  else if ( svgElem.hasAttribute( QStringLiteral( "viewbox" ) ) )
347  viewBox = svgElem.attribute( QStringLiteral( "viewbox" ), QString() );
348  }
349  }
350 
351  //could not find valid viewbox attribute
352  if ( viewBox.isEmpty() )
353  return 1.0;
354 
355  //width should be 3rd element in a 4 part space delimited string
356  QStringList parts = viewBox.split( ' ' );
357  if ( parts.count() != 4 )
358  return 1.0;
359 
360  bool heightOk = false;
361  double height = parts.at( 3 ).toDouble( &heightOk );
362 
363  bool widthOk = false;
364  double width = parts.at( 2 ).toDouble( &widthOk );
365  if ( widthOk )
366  {
367  if ( heightOk )
368  viewboxSize = QSizeF( width, height );
369  return width / entry->size;
370  }
371 
372  return 1.0;
373 }
374 
375 QByteArray QgsSvgCache::getImageData( const QString &path ) const
376 {
377  // is it a path to local file?
378  QFile svgFile( path );
379  if ( svgFile.exists() )
380  {
381  if ( svgFile.open( QIODevice::ReadOnly ) )
382  {
383  return svgFile.readAll();
384  }
385  else
386  {
387  return mMissingSvg;
388  }
389  }
390 
391  // maybe it's a url...
392  if ( !path.contains( QLatin1String( "://" ) ) ) // otherwise short, relative SVG paths might be considered URLs
393  {
394  return mMissingSvg;
395  }
396 
397  QUrl svgUrl( path );
398  if ( !svgUrl.isValid() )
399  {
400  return mMissingSvg;
401  }
402 
403  // check whether it's a url pointing to a local file
404  if ( svgUrl.scheme().compare( QLatin1String( "file" ), Qt::CaseInsensitive ) == 0 )
405  {
406  svgFile.setFileName( svgUrl.toLocalFile() );
407  if ( svgFile.exists() )
408  {
409  if ( svgFile.open( QIODevice::ReadOnly ) )
410  {
411  return svgFile.readAll();
412  }
413  }
414 
415  // not found...
416  return mMissingSvg;
417  }
418 
419  // the url points to a remote resource, download it!
420  QNetworkReply *reply = nullptr;
421 
422  // The following code blocks until the file is downloaded...
423  // TODO: use signals to get reply finished notification, in this moment
424  // it's executed while rendering.
425  while ( 1 )
426  {
427  QgsDebugMsg( QString( "get svg: %1" ).arg( svgUrl.toString() ) );
428  QNetworkRequest request( svgUrl );
429  request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
430  request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
431 
432  reply = QgsNetworkAccessManager::instance()->get( request );
433  connect( reply, &QNetworkReply::downloadProgress, this, &QgsSvgCache::downloadProgress );
434 
435  //emit statusChanged( tr( "Downloading svg." ) );
436 
437  // wait until the image download finished
438  // TODO: connect to the reply->finished() signal
439  while ( !reply->isFinished() )
440  {
441  QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents, 500 );
442  }
443 
444  if ( reply->error() != QNetworkReply::NoError )
445  {
446  QgsMessageLog::logMessage( tr( "SVG request failed [error: %1 - url: %2]" ).arg( reply->errorString(), reply->url().toString() ), tr( "SVG" ) );
447 
448  reply->deleteLater();
449  return QByteArray();
450  }
451 
452  QVariant redirect = reply->attribute( QNetworkRequest::RedirectionTargetAttribute );
453  if ( redirect.isNull() )
454  {
455  // neither network error nor redirection
456  // TODO: cache the image
457  break;
458  }
459 
460  // do a new request to the redirect url
461  svgUrl = redirect.toUrl();
462  reply->deleteLater();
463  }
464 
465  QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
466  if ( !status.isNull() && status.toInt() >= 400 )
467  {
468  QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
469  QgsMessageLog::logMessage( tr( "SVG request error [status: %1 - reason phrase: %2]" ).arg( status.toInt() ).arg( phrase.toString() ), tr( "SVG" ) );
470 
471  reply->deleteLater();
472  return mMissingSvg;
473  }
474 
475  QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
476  QgsDebugMsg( "contentType: " + contentType );
477  if ( !contentType.startsWith( QLatin1String( "image/svg+xml" ), Qt::CaseInsensitive ) )
478  {
479  reply->deleteLater();
480  return mMissingSvg;
481  }
482 
483  // read the image data
484  QByteArray ba = reply->readAll();
485  reply->deleteLater();
486 
487  return ba;
488 }
489 
491 {
492  if ( !entry )
493  {
494  return;
495  }
496 
497  delete entry->image;
498  entry->image = nullptr;
499 
500  QSvgRenderer r( entry->svgContent );
501  double hwRatio = 1.0;
502  if ( r.viewBoxF().width() > 0 )
503  {
504  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
505  }
506  double wSize = entry->size;
507  int wImgSize = static_cast< int >( wSize );
508  if ( wImgSize < 1 )
509  {
510  wImgSize = 1;
511  }
512  double hSize = wSize * hwRatio;
513  int hImgSize = static_cast< int >( hSize );
514  if ( hImgSize < 1 )
515  {
516  hImgSize = 1;
517  }
518  // cast double image sizes to int for QImage
519  QImage* image = new QImage( wImgSize, hImgSize, QImage::Format_ARGB32_Premultiplied );
520  image->fill( 0 ); // transparent background
521 
522  QPainter p( image );
523  if ( qgsDoubleNear( r.viewBoxF().width(), r.viewBoxF().height() ) )
524  {
525  r.render( &p );
526  }
527  else
528  {
529  QSizeF s( r.viewBoxF().size() );
530  s.scale( wSize, hSize, Qt::KeepAspectRatio );
531  QRectF rect(( wImgSize - s.width() ) / 2, ( hImgSize - s.height() ) / 2, s.width(), s.height() );
532  r.render( &p, rect );
533  }
534 
535  entry->image = image;
536  mTotalSize += ( image->width() * image->height() * 32 );
537 }
538 
539 void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry, bool forceVectorOutput )
540 {
541  Q_UNUSED( forceVectorOutput );
542  if ( !entry )
543  {
544  return;
545  }
546 
547  delete entry->picture;
548  entry->picture = nullptr;
549 
550  //correct QPictures dpi correction
551  QPicture* picture = new QPicture();
552  QRectF rect;
553  QSvgRenderer r( entry->svgContent );
554  double hwRatio = 1.0;
555  if ( r.viewBoxF().width() > 0 )
556  {
557  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
558  }
559 
560  double wSize = entry->size;
561  double hSize = wSize * hwRatio;
562  QSizeF s( r.viewBoxF().size() );
563  s.scale( wSize, hSize, Qt::KeepAspectRatio );
564  rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
565 
566  QPainter p( picture );
567  r.render( &p, rect );
568  entry->picture = picture;
569  mTotalSize += entry->picture->size();
570 }
571 
572 QgsSvgCacheEntry* QgsSvgCache::cacheEntry( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
573  double widthScaleFactor, double rasterScaleFactor )
574 {
575  //search entries in mEntryLookup
576  QgsSvgCacheEntry* currentEntry = nullptr;
577  QList<QgsSvgCacheEntry*> entries = mEntryLookup.values( file );
578 
579  QList<QgsSvgCacheEntry*>::iterator entryIt = entries.begin();
580  for ( ; entryIt != entries.end(); ++entryIt )
581  {
582  QgsSvgCacheEntry* cacheEntry = *entryIt;
583  if ( qgsDoubleNear( cacheEntry->size, size ) && cacheEntry->fill == fill && cacheEntry->outline == outline &&
584  qgsDoubleNear( cacheEntry->outlineWidth, outlineWidth ) && qgsDoubleNear( cacheEntry->widthScaleFactor, widthScaleFactor )
585  && qgsDoubleNear( cacheEntry->rasterScaleFactor, rasterScaleFactor ) )
586  {
587  currentEntry = cacheEntry;
588  break;
589  }
590  }
591 
592  //if not found: create new entry
593  //cache and replace params in svg content
594  if ( !currentEntry )
595  {
596  currentEntry = insertSVG( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
597  }
598  else
599  {
600  takeEntryFromList( currentEntry );
601  if ( !mMostRecentEntry ) //list is empty
602  {
603  mMostRecentEntry = currentEntry;
604  mLeastRecentEntry = currentEntry;
605  }
606  else
607  {
608  mMostRecentEntry->nextEntry = currentEntry;
609  currentEntry->previousEntry = mMostRecentEntry;
610  currentEntry->nextEntry = nullptr;
611  mMostRecentEntry = currentEntry;
612  }
613  }
614 
615  //debugging
616  //printEntryList();
617 
618  return currentEntry;
619 }
620 
621 void QgsSvgCache::replaceElemParams( QDomElement& elem, const QColor& fill, const QColor& outline, double outlineWidth )
622 {
623  if ( elem.isNull() )
624  {
625  return;
626  }
627 
628  //go through attributes
629  QDomNamedNodeMap attributes = elem.attributes();
630  int nAttributes = attributes.count();
631  for ( int i = 0; i < nAttributes; ++i )
632  {
633  QDomAttr attribute = attributes.item( i ).toAttr();
634  //e.g. style="fill:param(fill);param(stroke)"
635  if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
636  {
637  //entries separated by ';'
638  QString newAttributeString;
639 
640  QStringList entryList = attribute.value().split( ';' );
641  QStringList::const_iterator entryIt = entryList.constBegin();
642  for ( ; entryIt != entryList.constEnd(); ++entryIt )
643  {
644  QStringList keyValueSplit = entryIt->split( ':' );
645  if ( keyValueSplit.size() < 2 )
646  {
647  continue;
648  }
649  QString key = keyValueSplit.at( 0 );
650  QString value = keyValueSplit.at( 1 );
651  if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
652  {
653  value = fill.name();
654  }
655  else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
656  {
657  value = fill.alphaF();
658  }
659  else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
660  {
661  value = outline.name();
662  }
663  else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
664  {
665  value = outline.alphaF();
666  }
667  else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
668  {
669  value = QString::number( outlineWidth );
670  }
671 
672  if ( entryIt != entryList.constBegin() )
673  {
674  newAttributeString.append( ';' );
675  }
676  newAttributeString.append( key + ':' + value );
677  }
678  elem.setAttribute( attribute.name(), newAttributeString );
679  }
680  else
681  {
682  QString value = attribute.value();
683  if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
684  {
685  elem.setAttribute( attribute.name(), fill.name() );
686  }
687  else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
688  {
689  elem.setAttribute( attribute.name(), fill.alphaF() );
690  }
691  else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
692  {
693  elem.setAttribute( attribute.name(), outline.name() );
694  }
695  else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
696  {
697  elem.setAttribute( attribute.name(), outline.alphaF() );
698  }
699  else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
700  {
701  elem.setAttribute( attribute.name(), QString::number( outlineWidth ) );
702  }
703  }
704  }
705 
706  QDomNodeList childList = elem.childNodes();
707  int nChildren = childList.count();
708  for ( int i = 0; i < nChildren; ++i )
709  {
710  QDomElement childElem = childList.at( i ).toElement();
711  replaceElemParams( childElem, fill, outline, outlineWidth );
712  }
713 }
714 
715 void QgsSvgCache::containsElemParams( const QDomElement& elem, bool& hasFillParam, bool& hasDefaultFill, QColor& defaultFill,
716  bool& hasFillOpacityParam, bool& hasDefaultFillOpacity, double& defaultFillOpacity,
717  bool& hasOutlineParam, bool& hasDefaultOutline, QColor& defaultOutline,
718  bool& hasOutlineWidthParam, bool& hasDefaultOutlineWidth, double& defaultOutlineWidth,
719  bool& hasOutlineOpacityParam, bool& hasDefaultOutlineOpacity, double& defaultOutlineOpacity ) const
720 {
721  if ( elem.isNull() )
722  {
723  return;
724  }
725 
726  //we already have all the information, no need to go deeper
727  if ( hasFillParam && hasOutlineParam && hasOutlineWidthParam && hasFillOpacityParam && hasOutlineOpacityParam )
728  {
729  return;
730  }
731 
732  //check this elements attribute
733  QDomNamedNodeMap attributes = elem.attributes();
734  int nAttributes = attributes.count();
735 
736  QStringList valueSplit;
737  for ( int i = 0; i < nAttributes; ++i )
738  {
739  QDomAttr attribute = attributes.item( i ).toAttr();
740  if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
741  {
742  //entries separated by ';'
743  QStringList entryList = attribute.value().split( ';' );
744  QStringList::const_iterator entryIt = entryList.constBegin();
745  for ( ; entryIt != entryList.constEnd(); ++entryIt )
746  {
747  QStringList keyValueSplit = entryIt->split( ':' );
748  if ( keyValueSplit.size() < 2 )
749  {
750  continue;
751  }
752  QString value = keyValueSplit.at( 1 );
753  valueSplit = value.split( ' ' );
754  if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
755  {
756  hasFillParam = true;
757  if ( valueSplit.size() > 1 )
758  {
759  defaultFill = QColor( valueSplit.at( 1 ) );
760  hasDefaultFill = true;
761  }
762  }
763  else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
764  {
765  hasFillOpacityParam = true;
766  if ( valueSplit.size() > 1 )
767  {
768  bool ok;
769  double opacity = valueSplit.at( 1 ).toDouble( &ok );
770  if ( ok )
771  {
772  defaultFillOpacity = opacity;
773  hasDefaultFillOpacity = true;
774  }
775  }
776  }
777  else if ( !hasOutlineParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
778  {
779  hasOutlineParam = true;
780  if ( valueSplit.size() > 1 )
781  {
782  defaultOutline = QColor( valueSplit.at( 1 ) );
783  hasDefaultOutline = true;
784  }
785  }
786  else if ( !hasOutlineWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
787  {
788  hasOutlineWidthParam = true;
789  if ( valueSplit.size() > 1 )
790  {
791  defaultOutlineWidth = valueSplit.at( 1 ).toDouble();
792  hasDefaultOutlineWidth = true;
793  }
794  }
795  else if ( !hasOutlineOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
796  {
797  hasOutlineOpacityParam = true;
798  if ( valueSplit.size() > 1 )
799  {
800  bool ok;
801  double opacity = valueSplit.at( 1 ).toDouble( &ok );
802  if ( ok )
803  {
804  defaultOutlineOpacity = opacity;
805  hasDefaultOutlineOpacity = true;
806  }
807  }
808  }
809  }
810  }
811  else
812  {
813  QString value = attribute.value();
814  valueSplit = value.split( ' ' );
815  if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
816  {
817  hasFillParam = true;
818  if ( valueSplit.size() > 1 )
819  {
820  defaultFill = QColor( valueSplit.at( 1 ) );
821  hasDefaultFill = true;
822  }
823  }
824  else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
825  {
826  hasFillOpacityParam = true;
827  if ( valueSplit.size() > 1 )
828  {
829  bool ok;
830  double opacity = valueSplit.at( 1 ).toDouble( &ok );
831  if ( ok )
832  {
833  defaultFillOpacity = opacity;
834  hasDefaultFillOpacity = true;
835  }
836  }
837  }
838  else if ( !hasOutlineParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
839  {
840  hasOutlineParam = true;
841  if ( valueSplit.size() > 1 )
842  {
843  defaultOutline = QColor( valueSplit.at( 1 ) );
844  hasDefaultOutline = true;
845  }
846  }
847  else if ( !hasOutlineWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
848  {
849  hasOutlineWidthParam = true;
850  if ( valueSplit.size() > 1 )
851  {
852  defaultOutlineWidth = valueSplit.at( 1 ).toDouble();
853  hasDefaultOutlineWidth = true;
854  }
855  }
856  else if ( !hasOutlineOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
857  {
858  hasOutlineOpacityParam = true;
859  if ( valueSplit.size() > 1 )
860  {
861  bool ok;
862  double opacity = valueSplit.at( 1 ).toDouble( &ok );
863  if ( ok )
864  {
865  defaultOutlineOpacity = opacity;
866  hasDefaultOutlineOpacity = true;
867  }
868  }
869  }
870  }
871  }
872 
873  //pass it further to child items
874  QDomNodeList childList = elem.childNodes();
875  int nChildren = childList.count();
876  for ( int i = 0; i < nChildren; ++i )
877  {
878  QDomElement childElem = childList.at( i ).toElement();
879  containsElemParams( childElem, hasFillParam, hasDefaultFill, defaultFill,
880  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
881  hasOutlineParam, hasDefaultOutline, defaultOutline,
882  hasOutlineWidthParam, hasDefaultOutlineWidth, defaultOutlineWidth,
883  hasOutlineOpacityParam, hasDefaultOutlineOpacity, defaultOutlineOpacity );
884  }
885 }
886 
887 void QgsSvgCache::removeCacheEntry( const QString& s, QgsSvgCacheEntry* entry )
888 {
889  delete entry;
890  mEntryLookup.remove( s, entry );
891 }
892 
893 void QgsSvgCache::printEntryList()
894 {
895  QgsDebugMsg( "****************svg cache entry list*************************" );
896  QgsDebugMsg( "Cache size: " + QString::number( mTotalSize ) );
897  QgsSvgCacheEntry* entry = mLeastRecentEntry;
898  while ( entry )
899  {
900  QgsDebugMsg( "***Entry:" );
901  QgsDebugMsg( "File:" + entry->file );
902  QgsDebugMsg( "Size:" + QString::number( entry->size ) );
903  QgsDebugMsg( "Width scale factor" + QString::number( entry->widthScaleFactor ) );
904  QgsDebugMsg( "Raster scale factor" + QString::number( entry->rasterScaleFactor ) );
905  entry = entry->nextEntry;
906  }
907 }
908 
910 {
911  //only one entry in cache
912  if ( mLeastRecentEntry == mMostRecentEntry )
913  {
914  return;
915  }
916  QgsSvgCacheEntry* entry = mLeastRecentEntry;
917  while ( entry && ( mTotalSize > mMaximumSize ) )
918  {
919  QgsSvgCacheEntry* bkEntry = entry;
920  entry = entry->nextEntry;
921 
922  takeEntryFromList( bkEntry );
923  mEntryLookup.remove( bkEntry->lookupKey, bkEntry );
924  mTotalSize -= bkEntry->dataSize();
925  delete bkEntry;
926  }
927 }
928 
930 {
931  if ( !entry )
932  {
933  return;
934  }
935 
936  if ( entry->previousEntry )
937  {
938  entry->previousEntry->nextEntry = entry->nextEntry;
939  }
940  else
941  {
942  mLeastRecentEntry = entry->nextEntry;
943  }
944  if ( entry->nextEntry )
945  {
946  entry->nextEntry->previousEntry = entry->previousEntry;
947  }
948  else
949  {
950  mMostRecentEntry = entry->previousEntry;
951  }
952 }
953 
954 void QgsSvgCache::downloadProgress( qint64 bytesReceived, qint64 bytesTotal )
955 {
956  QString msg = tr( "%1 of %2 bytes of svg image downloaded." ).arg( bytesReceived ).arg( bytesTotal < 0 ? QStringLiteral( "unknown number of" ) : QString::number( bytesTotal ) );
957  QgsDebugMsg( msg );
958  emit statusChanged( msg );
959 }
QgsSvgCacheEntry * previousEntry
Definition: qgssvgcache.h:78
int dataSize() const
Return memory usage in bytes.
Definition: qgssvgcache.cpp:83
QSizeF viewboxSize
SVG viewbox size.
Definition: qgssvgcache.h:67
QImage * image
Definition: qgssvgcache.h:71
const QPicture & svgAsPicture(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor, bool forceVectorOutput=false)
Get SVG as QPicture&.
QSizeF svgViewboxSize(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor)
Calculates the viewbox size of a (possibly cached) SVG file.
QgsSvgCacheEntry * cacheEntry(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor)
Returns entry from cache or creates a new entry if it does not exist already.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
A cache for images / pictures derived from svg files.
Definition: qgssvgcache.h:96
static QString symbolNameToPath(QString name)
Get symbol&#39;s path from its name.
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:196
void containsParams(const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasOutlineParam, QColor &defaultOutlineColor, bool &hasOutlineWidthParam, double &defaultOutlineWidth) const
Tests if an svg file contains parameters for fill, outline color, outline width.
static QgsSvgCache * instance()
Definition: qgssvgcache.cpp:97
const QImage & svgAsImage(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor, bool &fitsInCache)
Get SVG as QImage.
QPicture * picture
Definition: qgssvgcache.h:72
QByteArray getImageData(const QString &path) const
Get image data.
static void logMessage(const QString &message, const QString &tag=QString::null, MessageLevel level=WARNING)
add a message to the instance (and create it if necessary)
void statusChanged(const QString &theStatusQString)
Emit a signal to be caught by qgisapp and display a msg on status bar.
QgsSvgCache(QObject *parent=nullptr)
protected constructor
const QByteArray & svgContent(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor)
Get SVG content.
void cachePicture(QgsSvgCacheEntry *entry, bool forceVectorOutput=false)
void trimToMaximumSize()
Removes the least used items until the maximum size is under the limit.
void takeEntryFromList(QgsSvgCacheEntry *entry)
QString lookupKey
Lookup key used by QgsSvgCache&#39;s hashtable (relative or absolute path). Needed for removal from the h...
Definition: qgssvgcache.h:58
void cacheImage(QgsSvgCacheEntry *entry)
void replaceParamsAndCacheSvg(QgsSvgCacheEntry *entry)
static QgsNetworkAccessManager * instance()
returns a pointer to the single instance
double outlineWidth
Definition: qgssvgcache.h:60
QgsSvgCacheEntry * insertSVG(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor)
Creates new cache entry and returns pointer to it.
double rasterScaleFactor
Definition: qgssvgcache.h:62
double widthScaleFactor
Definition: qgssvgcache.h:61
QString file
Absolute path to SVG file.
Definition: qgssvgcache.h:56
QByteArray svgContent
Definition: qgssvgcache.h:74
bool operator==(const QgsSvgCacheEntry &other) const
Don&#39;t consider image, picture, last used timestamp for comparison.
Definition: qgssvgcache.cpp:77
QgsSvgCacheEntry * nextEntry
Definition: qgssvgcache.h:77