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