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