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