QGIS API Documentation  2.15.0-Master (94d88e6)
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 "qgssymbollayerv2utils.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 
104  : QObject( parent )
105  , mTotalSize( 0 )
106  , mLeastRecentEntry( nullptr )
107  , mMostRecentEntry( nullptr )
108 {
109  mMissingSvg = QString( "<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toAscii();
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)
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 
254  bool& hasFillParam, bool& hasDefaultFillParam, QColor& defaultFillColor,
255  bool& hasOutlineParam, bool& hasDefaultOutlineColor, QColor& defaultOutlineColor,
256  bool& hasOutlineWidthParam, bool& hasDefaultOutlineWidth, double& defaultOutlineWidth ) const
257 {
258  bool hasFillOpacityParam = false;
259  bool hasDefaultFillOpacity = false;
260  double defaultFillOpacity = 1.0;
261  bool hasOutlineOpacityParam = false;
262  bool hasDefaultOutlineOpacity = false;
263  double defaultOutlineOpacity = 1.0;
264 
265  containsParams( path, hasFillParam, hasDefaultFillParam, defaultFillColor,
266  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
267  hasOutlineParam, hasDefaultOutlineColor, defaultOutlineColor,
268  hasOutlineWidthParam, hasDefaultOutlineWidth, defaultOutlineWidth,
269  hasOutlineOpacityParam, hasDefaultOutlineOpacity, defaultOutlineOpacity );
270 }
271 
273  bool& hasFillParam, bool& hasDefaultFillParam, QColor& defaultFillColor,
274  bool& hasFillOpacityParam, bool& hasDefaultFillOpacity, double& defaultFillOpacity,
275  bool& hasOutlineParam, bool& hasDefaultOutlineColor, QColor& defaultOutlineColor,
276  bool& hasOutlineWidthParam, bool& hasDefaultOutlineWidth, double& defaultOutlineWidth,
277  bool& hasOutlineOpacityParam, bool& hasDefaultOutlineOpacity, double& defaultOutlineOpacity ) const
278 {
279  hasFillParam = false;
280  hasFillOpacityParam = false;
281  hasOutlineParam = false;
282  hasOutlineWidthParam = false;
283  hasOutlineOpacityParam = false;
284  defaultFillColor = QColor( Qt::white );
285  defaultFillOpacity = 1.0;
286  defaultOutlineColor = QColor( Qt::black );
287  defaultOutlineWidth = 0.2;
288  defaultOutlineOpacity = 1.0;
289 
290  hasDefaultFillParam = false;
291  hasDefaultFillOpacity = false;
292  hasDefaultOutlineColor = false;
293  hasDefaultOutlineWidth = false;
294  hasDefaultOutlineOpacity = false;
295 
296  QDomDocument svgDoc;
297  if ( !svgDoc.setContent( getImageData( path ) ) )
298  {
299  return;
300  }
301 
302  QDomElement docElem = svgDoc.documentElement();
303  containsElemParams( docElem, hasFillParam, hasDefaultFillParam, defaultFillColor,
304  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
305  hasOutlineParam, hasDefaultOutlineColor, defaultOutlineColor,
306  hasOutlineWidthParam, hasDefaultOutlineWidth, defaultOutlineWidth,
307  hasOutlineOpacityParam, hasDefaultOutlineOpacity, defaultOutlineOpacity );
308 }
309 
311 {
312  if ( !entry )
313  {
314  return;
315  }
316 
317  QDomDocument svgDoc;
318  if ( !svgDoc.setContent( getImageData( entry->file ) ) )
319  {
320  return;
321  }
322 
323  //replace fill color, outline color, outline with in all nodes
324  QDomElement docElem = svgDoc.documentElement();
325 
326  QSizeF viewboxSize;
327  double sizeScaleFactor = calcSizeScaleFactor( entry, docElem, viewboxSize );
328  entry->viewboxSize = viewboxSize;
329  replaceElemParams( docElem, entry->fill, entry->outline, entry->outlineWidth * sizeScaleFactor );
330 
331  entry->svgContent = svgDoc.toByteArray();
332  mTotalSize += entry->svgContent.size();
333 }
334 
335 double QgsSvgCache::calcSizeScaleFactor( QgsSvgCacheEntry* entry, const QDomElement& docElem, QSizeF& viewboxSize ) const
336 {
337  QString viewBox;
338 
339  //bad size
340  if ( !entry || qgsDoubleNear( entry->size, 0.0 ) )
341  return 1.0;
342 
343  //find svg viewbox attribute
344  //first check if docElem is svg element
345  if ( docElem.tagName() == "svg" )
346  {
347  viewBox = docElem.attribute( "viewBox", QString() );
348  }
349  else
350  {
351  QDomElement svgElem = docElem.firstChildElement( "svg" ) ;
352  if ( !svgElem.isNull() )
353  {
354  viewBox = svgElem.attribute( "viewBox", QString() );
355  }
356  }
357 
358  //could not find valid viewbox attribute
359  if ( viewBox.isEmpty() )
360  return 1.0;
361 
362  //width should be 3rd element in a 4 part space delimited string
363  QStringList parts = viewBox.split( ' ' );
364  if ( parts.count() != 4 )
365  return 1.0;
366 
367  bool heightOk = false;
368  double height = parts.at( 3 ).toDouble( &heightOk );
369 
370  bool widthOk = false;
371  double width = parts.at( 2 ).toDouble( &widthOk );
372  if ( widthOk )
373  {
374  if ( heightOk )
375  viewboxSize = QSizeF( width, height );
376  return width / entry->size;
377  }
378 
379  return 1.0;
380 }
381 
383 {
384  // is it a path to local file?
385  QFile svgFile( path );
386  if ( svgFile.exists() )
387  {
388  if ( svgFile.open( QIODevice::ReadOnly ) )
389  {
390  return svgFile.readAll();
391  }
392  else
393  {
394  return mMissingSvg;
395  }
396  }
397 
398  // maybe it's a url...
399  if ( !path.contains( "://" ) ) // otherwise short, relative SVG paths might be considered URLs
400  {
401  return mMissingSvg;
402  }
403 
404  QUrl svgUrl( path );
405  if ( !svgUrl.isValid() )
406  {
407  return mMissingSvg;
408  }
409 
410  // check whether it's a url pointing to a local file
411  if ( svgUrl.scheme().compare( "file", Qt::CaseInsensitive ) == 0 )
412  {
413  svgFile.setFileName( svgUrl.toLocalFile() );
414  if ( svgFile.exists() )
415  {
416  if ( svgFile.open( QIODevice::ReadOnly ) )
417  {
418  return svgFile.readAll();
419  }
420  }
421 
422  // not found...
423  return mMissingSvg;
424  }
425 
426  // the url points to a remote resource, download it!
427  QNetworkReply *reply = nullptr;
428 
429  // The following code blocks until the file is downloaded...
430  // TODO: use signals to get reply finished notification, in this moment
431  // it's executed while rendering.
432  while ( 1 )
433  {
434  QgsDebugMsg( QString( "get svg: %1" ).arg( svgUrl.toString() ) );
435  QNetworkRequest request( svgUrl );
436  request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
437  request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
438 
439  reply = QgsNetworkAccessManager::instance()->get( request );
440  connect( reply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( downloadProgress( qint64, qint64 ) ) );
441 
442  //emit statusChanged( tr( "Downloading svg." ) );
443 
444  // wait until the image download finished
445  // TODO: connect to the reply->finished() signal
446  while ( !reply->isFinished() )
447  {
448  QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents, 500 );
449  }
450 
451  if ( reply->error() != QNetworkReply::NoError )
452  {
453  QgsMessageLog::logMessage( tr( "SVG request failed [error: %1 - url: %2]" ).arg( reply->errorString(), reply->url().toString() ), tr( "SVG" ) );
454 
455  reply->deleteLater();
456  return QByteArray();
457  }
458 
459  QVariant redirect = reply->attribute( QNetworkRequest::RedirectionTargetAttribute );
460  if ( redirect.isNull() )
461  {
462  // neither network error nor redirection
463  // TODO: cache the image
464  break;
465  }
466 
467  // do a new request to the redirect url
468  svgUrl = redirect.toUrl();
469  reply->deleteLater();
470  }
471 
472  QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
473  if ( !status.isNull() && status.toInt() >= 400 )
474  {
475  QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
476  QgsMessageLog::logMessage( tr( "SVG request error [status: %1 - reason phrase: %2]" ).arg( status.toInt() ).arg( phrase.toString() ), tr( "SVG" ) );
477 
478  reply->deleteLater();
479  return mMissingSvg;
480  }
481 
482  QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
483  QgsDebugMsg( "contentType: " + contentType );
484  if ( !contentType.startsWith( "image/svg+xml", Qt::CaseInsensitive ) )
485  {
486  reply->deleteLater();
487  return mMissingSvg;
488  }
489 
490  // read the image data
491  QByteArray ba = reply->readAll();
492  reply->deleteLater();
493 
494  return ba;
495 }
496 
498 {
499  if ( !entry )
500  {
501  return;
502  }
503 
504  delete entry->image;
505  entry->image = nullptr;
506 
507  QSvgRenderer r( entry->svgContent );
508  double hwRatio = 1.0;
509  if ( r.viewBoxF().width() > 0 )
510  {
511  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
512  }
513  double wSize = entry->size;
514  int wImgSize = static_cast< int >( wSize );
515  if ( wImgSize < 1 )
516  {
517  wImgSize = 1;
518  }
519  double hSize = wSize * hwRatio;
520  int hImgSize = static_cast< int >( hSize );
521  if ( hImgSize < 1 )
522  {
523  hImgSize = 1;
524  }
525  // cast double image sizes to int for QImage
526  QImage* image = new QImage( wImgSize, hImgSize, QImage::Format_ARGB32_Premultiplied );
527  image->fill( 0 ); // transparent background
528 
529  QPainter p( image );
530  if ( qgsDoubleNear( r.viewBoxF().width(), r.viewBoxF().height() ) )
531  {
532  r.render( &p );
533  }
534  else
535  {
536  QSizeF s( r.viewBoxF().size() );
537  s.scale( wSize, hSize, Qt::KeepAspectRatio );
538  QRectF rect(( wImgSize - s.width() ) / 2, ( hImgSize - s.height() ) / 2, s.width(), s.height() );
539  r.render( &p, rect );
540  }
541 
542  entry->image = image;
543  mTotalSize += ( image->width() * image->height() * 32 );
544 }
545 
546 void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry, bool forceVectorOutput )
547 {
548  Q_UNUSED( forceVectorOutput );
549  if ( !entry )
550  {
551  return;
552  }
553 
554  delete entry->picture;
555  entry->picture = nullptr;
556 
557  //correct QPictures dpi correction
558  QPicture* picture = new QPicture();
559  QRectF rect;
560  QSvgRenderer r( entry->svgContent );
561  double hwRatio = 1.0;
562  if ( r.viewBoxF().width() > 0 )
563  {
564  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
565  }
566 
567  double wSize = entry->size;
568  double hSize = wSize * hwRatio;
569  QSizeF s( r.viewBoxF().size() );
570  s.scale( wSize, hSize, Qt::KeepAspectRatio );
571  rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
572 
573  QPainter p( picture );
574  r.render( &p, rect );
575  entry->picture = picture;
576  mTotalSize += entry->picture->size();
577 }
578 
579 QgsSvgCacheEntry* QgsSvgCache::cacheEntry( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
580  double widthScaleFactor, double rasterScaleFactor )
581 {
582  //search entries in mEntryLookup
583  QgsSvgCacheEntry* currentEntry = nullptr;
584  QList<QgsSvgCacheEntry*> entries = mEntryLookup.values( file );
585 
586  QList<QgsSvgCacheEntry*>::iterator entryIt = entries.begin();
587  for ( ; entryIt != entries.end(); ++entryIt )
588  {
589  QgsSvgCacheEntry* cacheEntry = *entryIt;
590  if ( qgsDoubleNear( cacheEntry->size, size ) && cacheEntry->fill == fill && cacheEntry->outline == outline &&
591  qgsDoubleNear( cacheEntry->outlineWidth, outlineWidth ) && qgsDoubleNear( cacheEntry->widthScaleFactor, widthScaleFactor )
592  && qgsDoubleNear( cacheEntry->rasterScaleFactor, rasterScaleFactor ) )
593  {
594  currentEntry = cacheEntry;
595  break;
596  }
597  }
598 
599  //if not found: create new entry
600  //cache and replace params in svg content
601  if ( !currentEntry )
602  {
603  currentEntry = insertSVG( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
604  }
605  else
606  {
607  takeEntryFromList( currentEntry );
608  if ( !mMostRecentEntry ) //list is empty
609  {
610  mMostRecentEntry = currentEntry;
611  mLeastRecentEntry = currentEntry;
612  }
613  else
614  {
615  mMostRecentEntry->nextEntry = currentEntry;
616  currentEntry->previousEntry = mMostRecentEntry;
617  currentEntry->nextEntry = nullptr;
618  mMostRecentEntry = currentEntry;
619  }
620  }
621 
622  //debugging
623  //printEntryList();
624 
625  return currentEntry;
626 }
627 
628 void QgsSvgCache::replaceElemParams( QDomElement& elem, const QColor& fill, const QColor& outline, double outlineWidth )
629 {
630  if ( elem.isNull() )
631  {
632  return;
633  }
634 
635  //go through attributes
636  QDomNamedNodeMap attributes = elem.attributes();
637  int nAttributes = attributes.count();
638  for ( int i = 0; i < nAttributes; ++i )
639  {
640  QDomAttr attribute = attributes.item( i ).toAttr();
641  //e.g. style="fill:param(fill);param(stroke)"
642  if ( attribute.name().compare( "style", Qt::CaseInsensitive ) == 0 )
643  {
644  //entries separated by ';'
645  QString newAttributeString;
646 
647  QStringList entryList = attribute.value().split( ';' );
648  QStringList::const_iterator entryIt = entryList.constBegin();
649  for ( ; entryIt != entryList.constEnd(); ++entryIt )
650  {
651  QStringList keyValueSplit = entryIt->split( ':' );
652  if ( keyValueSplit.size() < 2 )
653  {
654  continue;
655  }
656  QString key = keyValueSplit.at( 0 );
657  QString value = keyValueSplit.at( 1 );
658  if ( value.startsWith( "param(fill)" ) )
659  {
660  value = fill.name();
661  }
662  else if ( value.startsWith( "param(fill-opacity)" ) )
663  {
664  value = fill.alphaF();
665  }
666  else if ( value.startsWith( "param(outline)" ) )
667  {
668  value = outline.name();
669  }
670  else if ( value.startsWith( "param(outline-opacity)" ) )
671  {
672  value = outline.alphaF();
673  }
674  else if ( value.startsWith( "param(outline-width)" ) )
675  {
676  value = QString::number( outlineWidth );
677  }
678 
679  if ( entryIt != entryList.constBegin() )
680  {
681  newAttributeString.append( ';' );
682  }
683  newAttributeString.append( key + ':' + value );
684  }
685  elem.setAttribute( attribute.name(), newAttributeString );
686  }
687  else
688  {
689  QString value = attribute.value();
690  if ( value.startsWith( "param(fill)" ) )
691  {
692  elem.setAttribute( attribute.name(), fill.name() );
693  }
694  else if ( value.startsWith( "param(fill-opacity)" ) )
695  {
696  elem.setAttribute( attribute.name(), fill.alphaF() );
697  }
698  else if ( value.startsWith( "param(outline)" ) )
699  {
700  elem.setAttribute( attribute.name(), outline.name() );
701  }
702  else if ( value.startsWith( "param(outline-opacity)" ) )
703  {
704  elem.setAttribute( attribute.name(), outline.alphaF() );
705  }
706  else if ( value.startsWith( "param(outline-width)" ) )
707  {
708  elem.setAttribute( attribute.name(), QString::number( outlineWidth ) );
709  }
710  }
711  }
712 
713  QDomNodeList childList = elem.childNodes();
714  int nChildren = childList.count();
715  for ( int i = 0; i < nChildren; ++i )
716  {
717  QDomElement childElem = childList.at( i ).toElement();
718  replaceElemParams( childElem, fill, outline, outlineWidth );
719  }
720 }
721 
722 void QgsSvgCache::containsElemParams( const QDomElement& elem, bool& hasFillParam, bool& hasDefaultFill, QColor& defaultFill,
723  bool& hasFillOpacityParam, bool& hasDefaultFillOpacity, double& defaultFillOpacity,
724  bool& hasOutlineParam, bool& hasDefaultOutline, QColor& defaultOutline,
725  bool& hasOutlineWidthParam, bool& hasDefaultOutlineWidth, double& defaultOutlineWidth,
726  bool& hasOutlineOpacityParam, bool& hasDefaultOutlineOpacity, double& defaultOutlineOpacity ) const
727 {
728  if ( elem.isNull() )
729  {
730  return;
731  }
732 
733  //we already have all the information, no need to go deeper
734  if ( hasFillParam && hasOutlineParam && hasOutlineWidthParam && hasFillOpacityParam && hasOutlineOpacityParam )
735  {
736  return;
737  }
738 
739  //check this elements attribute
740  QDomNamedNodeMap attributes = elem.attributes();
741  int nAttributes = attributes.count();
742 
743  QStringList valueSplit;
744  for ( int i = 0; i < nAttributes; ++i )
745  {
746  QDomAttr attribute = attributes.item( i ).toAttr();
747  if ( attribute.name().compare( "style", Qt::CaseInsensitive ) == 0 )
748  {
749  //entries separated by ';'
750  QStringList entryList = attribute.value().split( ';' );
751  QStringList::const_iterator entryIt = entryList.constBegin();
752  for ( ; entryIt != entryList.constEnd(); ++entryIt )
753  {
754  QStringList keyValueSplit = entryIt->split( ':' );
755  if ( keyValueSplit.size() < 2 )
756  {
757  continue;
758  }
759  QString value = keyValueSplit.at( 1 );
760  valueSplit = value.split( ' ' );
761  if ( !hasFillParam && value.startsWith( "param(fill)" ) )
762  {
763  hasFillParam = true;
764  if ( valueSplit.size() > 1 )
765  {
766  defaultFill = QColor( valueSplit.at( 1 ) );
767  hasDefaultFill = true;
768  }
769  }
770  else if ( !hasFillOpacityParam && value.startsWith( "param(fill-opacity)" ) )
771  {
772  hasFillOpacityParam = true;
773  if ( valueSplit.size() > 1 )
774  {
775  bool ok;
776  double opacity = valueSplit.at( 1 ).toDouble( &ok );
777  if ( ok )
778  {
779  defaultFillOpacity = opacity;
780  hasDefaultFillOpacity = true;
781  }
782  }
783  }
784  else if ( !hasOutlineParam && value.startsWith( "param(outline)" ) )
785  {
786  hasOutlineParam = true;
787  if ( valueSplit.size() > 1 )
788  {
789  defaultOutline = QColor( valueSplit.at( 1 ) );
790  hasDefaultOutline = true;
791  }
792  }
793  else if ( !hasOutlineWidthParam && value.startsWith( "param(outline-width)" ) )
794  {
795  hasOutlineWidthParam = true;
796  if ( valueSplit.size() > 1 )
797  {
798  defaultOutlineWidth = valueSplit.at( 1 ).toDouble();
799  hasDefaultOutlineWidth = true;
800  }
801  }
802  else if ( !hasOutlineOpacityParam && value.startsWith( "param(outline-opacity)" ) )
803  {
804  hasOutlineOpacityParam = true;
805  if ( valueSplit.size() > 1 )
806  {
807  bool ok;
808  double opacity = valueSplit.at( 1 ).toDouble( &ok );
809  if ( ok )
810  {
811  defaultOutlineOpacity = opacity;
812  hasDefaultOutlineOpacity = true;
813  }
814  }
815  }
816  }
817  }
818  else
819  {
820  QString value = attribute.value();
821  valueSplit = value.split( ' ' );
822  if ( !hasFillParam && value.startsWith( "param(fill)" ) )
823  {
824  hasFillParam = true;
825  if ( valueSplit.size() > 1 )
826  {
827  defaultFill = QColor( valueSplit.at( 1 ) );
828  hasDefaultFill = true;
829  }
830  }
831  else if ( !hasFillOpacityParam && value.startsWith( "param(fill-opacity)" ) )
832  {
833  hasFillOpacityParam = true;
834  if ( valueSplit.size() > 1 )
835  {
836  bool ok;
837  double opacity = valueSplit.at( 1 ).toDouble( &ok );
838  if ( ok )
839  {
840  defaultFillOpacity = opacity;
841  hasDefaultFillOpacity = true;
842  }
843  }
844  }
845  else if ( !hasOutlineParam && value.startsWith( "param(outline)" ) )
846  {
847  hasOutlineParam = true;
848  if ( valueSplit.size() > 1 )
849  {
850  defaultOutline = QColor( valueSplit.at( 1 ) );
851  hasDefaultOutline = true;
852  }
853  }
854  else if ( !hasOutlineWidthParam && value.startsWith( "param(outline-width)" ) )
855  {
856  hasOutlineWidthParam = true;
857  if ( valueSplit.size() > 1 )
858  {
859  defaultOutlineWidth = valueSplit.at( 1 ).toDouble();
860  hasDefaultOutlineWidth = true;
861  }
862  }
863  else if ( !hasOutlineOpacityParam && value.startsWith( "param(outline-opacity)" ) )
864  {
865  hasOutlineOpacityParam = true;
866  if ( valueSplit.size() > 1 )
867  {
868  bool ok;
869  double opacity = valueSplit.at( 1 ).toDouble( &ok );
870  if ( ok )
871  {
872  defaultOutlineOpacity = opacity;
873  hasDefaultOutlineOpacity = true;
874  }
875  }
876  }
877  }
878  }
879 
880  //pass it further to child items
881  QDomNodeList childList = elem.childNodes();
882  int nChildren = childList.count();
883  for ( int i = 0; i < nChildren; ++i )
884  {
885  QDomElement childElem = childList.at( i ).toElement();
886  containsElemParams( childElem, hasFillParam, hasDefaultFill, defaultFill,
887  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
888  hasOutlineParam, hasDefaultOutline, defaultOutline,
889  hasOutlineWidthParam, hasDefaultOutlineWidth, defaultOutlineWidth,
890  hasOutlineOpacityParam, hasDefaultOutlineOpacity, defaultOutlineOpacity );
891  }
892 }
893 
894 void QgsSvgCache::removeCacheEntry( const QString& s, QgsSvgCacheEntry* entry )
895 {
896  delete entry;
897  mEntryLookup.remove( s, entry );
898 }
899 
900 void QgsSvgCache::printEntryList()
901 {
902  QgsDebugMsg( "****************svg cache entry list*************************" );
903  QgsDebugMsg( "Cache size: " + QString::number( mTotalSize ) );
904  QgsSvgCacheEntry* entry = mLeastRecentEntry;
905  while ( entry )
906  {
907  QgsDebugMsg( "***Entry:" );
908  QgsDebugMsg( "File:" + entry->file );
909  QgsDebugMsg( "Size:" + QString::number( entry->size ) );
910  QgsDebugMsg( "Width scale factor" + QString::number( entry->widthScaleFactor ) );
911  QgsDebugMsg( "Raster scale factor" + QString::number( entry->rasterScaleFactor ) );
912  entry = entry->nextEntry;
913  }
914 }
915 
917 {
918  //only one entry in cache
919  if ( mLeastRecentEntry == mMostRecentEntry )
920  {
921  return;
922  }
923  QgsSvgCacheEntry* entry = mLeastRecentEntry;
924  while ( entry && ( mTotalSize > mMaximumSize ) )
925  {
926  QgsSvgCacheEntry* bkEntry = entry;
927  entry = entry->nextEntry;
928 
929  takeEntryFromList( bkEntry );
930  mEntryLookup.remove( bkEntry->lookupKey, bkEntry );
931  mTotalSize -= bkEntry->dataSize();
932  delete bkEntry;
933  }
934 }
935 
937 {
938  if ( !entry )
939  {
940  return;
941  }
942 
943  if ( entry->previousEntry )
944  {
945  entry->previousEntry->nextEntry = entry->nextEntry;
946  }
947  else
948  {
949  mLeastRecentEntry = entry->nextEntry;
950  }
951  if ( entry->nextEntry )
952  {
953  entry->nextEntry->previousEntry = entry->previousEntry;
954  }
955  else
956  {
957  mMostRecentEntry = entry->previousEntry;
958  }
959 }
960 
961 void QgsSvgCache::downloadProgress( qint64 bytesReceived, qint64 bytesTotal )
962 {
963  QString msg = tr( "%1 of %2 bytes of svg image downloaded." ).arg( bytesReceived ).arg( bytesTotal < 0 ? QString( "unknown number of" ) : QString::number( bytesTotal ) );
964  QgsDebugMsg( msg );
965  emit statusChanged( msg );
966 }
QgsSvgCacheEntry * previousEntry
Definition: qgssvgcache.h:74
QUrl toUrl() const
QString & append(QChar ch)
int dataSize() const
Return memory usage in bytes.
Definition: qgssvgcache.cpp:83
QSizeF viewboxSize
SVG viewbox size.
Definition: qgssvgcache.h:63
void scale(qreal width, qreal height, Qt::AspectRatioMode mode)
QImage * image
Definition: qgssvgcache.h:67
qreal alphaF() const
QString name() const
void render(QPainter *painter)
QString name() const
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.
QString attribute(const QString &name, const QString &defValue) const
QString errorString() const
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
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
QSizeF size() const
const T & at(int i) const
A cache for images / pictures derived from svg files.
Definition: qgssvgcache.h:90
void setFileName(const QString &name)
QDomElement documentElement() const
bool exists() const
QDomNodeList childNodes() const
QString toString(QFlags< QUrl::FormattingOption > options) const
QString tr(const char *sourceText, const char *disambiguation, int n)
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:352
int size() const
uint size() const
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
QDomElement toElement() const
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:68
int count() const
QString number(int n, int base)
int count(const T &value) const
void processEvents(QFlags< QEventLoop::ProcessEventsFlag > flags)
int toInt(bool *ok) const
bool isNull() const
void fill(uint pixelValue)
QByteArray getImageData(const QString &path) const
Get image data.
int width() const
void setAttribute(const QString &name, const QString &value)
bool isFinished() const
bool isEmpty() const
int remove(const Key &key)
static QString symbolNameToPath(QString name)
Get symbol&#39;s path from its name.
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
QByteArray readAll()
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.
int count() const
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 deleteLater()
void cachePicture(QgsSvgCacheEntry *entry, bool forceVectorOutput=false)
QHash< Key, T >::iterator insert(const Key &key, const T &value)
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
QString scheme() const
QString toLocalFile() const
iterator end()
bool contains(QChar ch, Qt::CaseSensitivity cs) const
QString value() const
bool isNull() const
void trimToMaximumSize()
Removes the least used items until the maximum size is under the limit.
QVariant header(QNetworkRequest::KnownHeaders header) const
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:54
bool isValid() const
void cacheImage(QgsSvgCacheEntry *entry)
qreal width() const
void replaceParamsAndCacheSvg(QgsSvgCacheEntry *entry)
static QgsNetworkAccessManager * instance()
returns a pointer to the single instance
double outlineWidth
Definition: qgssvgcache.h:56
QDomElement firstChildElement(const QString &tagName) const
QDomAttr toAttr() const
QVariant attribute(QNetworkRequest::Attribute code) const
QUrl url() const
void setAttribute(Attribute code, const QVariant &value)
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.
NetworkError error() const
qreal height() const
int height() const
double rasterScaleFactor
Definition: qgssvgcache.h:58
QString tagName() const
QNetworkReply * get(const QNetworkRequest &request)
const_iterator constEnd() const
const_iterator constBegin() const
QDomNode item(int index) const
int size() const
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
int compare(const QString &other) const
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const
double widthScaleFactor
Definition: qgssvgcache.h:57
iterator begin()
QRectF viewBoxF() const
QString file
Absolute path to SVG file.
Definition: qgssvgcache.h:52
QByteArray toAscii() const
QByteArray svgContent
Definition: qgssvgcache.h:70
QByteArray toByteArray(int indent) const
QDomNode at(int index) const
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
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:73
QDomNamedNodeMap attributes() const