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