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