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