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