QGIS API Documentation  3.6.0-Noosa (5873452)
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"
25 
26 #include <QApplication>
27 #include <QCoreApplication>
28 #include <QCursor>
29 #include <QDomDocument>
30 #include <QDomElement>
31 #include <QFile>
32 #include <QImage>
33 #include <QPainter>
34 #include <QPicture>
35 #include <QSvgRenderer>
36 #include <QFileInfo>
37 #include <QNetworkReply>
38 #include <QNetworkRequest>
39 
41 
42 //
43 // QgsSvgCacheEntry
44 //
45 
46 QgsSvgCacheEntry::QgsSvgCacheEntry( const QString &path, double size, double strokeWidth, double widthScaleFactor, const QColor &fill, const QColor &stroke, double fixedAspectRatio )
48  , size( size )
49  , strokeWidth( strokeWidth )
50  , widthScaleFactor( widthScaleFactor )
51  , fixedAspectRatio( fixedAspectRatio )
52  , fill( fill )
53  , stroke( stroke )
54 {
55 }
56 
57 bool QgsSvgCacheEntry::isEqual( const QgsAbstractContentCacheEntry *other ) const
58 {
59  const QgsSvgCacheEntry *otherSvg = dynamic_cast< const QgsSvgCacheEntry * >( other );
60  // cheapest checks first!
61  if ( !otherSvg
62  || !qgsDoubleNear( otherSvg->fixedAspectRatio, fixedAspectRatio )
63  || !qgsDoubleNear( otherSvg->size, size )
64  || !qgsDoubleNear( otherSvg->strokeWidth, strokeWidth )
65  || !qgsDoubleNear( otherSvg->widthScaleFactor, widthScaleFactor )
66  || otherSvg->fill != fill
67  || otherSvg->stroke != stroke
68  || otherSvg->path != path )
69  return false;
70 
71  return true;
72 }
73 
74 int QgsSvgCacheEntry::dataSize() const
75 {
76  int size = svgContent.size();
77  if ( picture )
78  {
79  size += picture->size();
80  }
81  if ( image )
82  {
83  size += ( image->width() * image->height() * 32 );
84  }
85  return size;
86 }
87 
88 void QgsSvgCacheEntry::dump() const
89 {
90  QgsDebugMsg( QStringLiteral( "path: %1, size %2, width scale factor %3" ).arg( path ).arg( size ).arg( widthScaleFactor ) );
91 }
93 
94 
95 //
96 // QgsSvgCache
97 //
98 
99 QgsSvgCache::QgsSvgCache( QObject *parent )
100  : QgsAbstractContentCache< QgsSvgCacheEntry >( parent, QObject::tr( "SVG" ) )
101 {
102  mMissingSvg = QStringLiteral( "<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
103 
104  const QString downloadingSvgPath = QgsApplication::defaultThemePath() + QStringLiteral( "downloading_svg.svg" );
105  if ( QFile::exists( downloadingSvgPath ) )
106  {
107  QFile file( downloadingSvgPath );
108  if ( file.open( QIODevice::ReadOnly ) )
109  {
110  mFetchingSvg = file.readAll();
111  }
112  }
113 
114  if ( mFetchingSvg.isEmpty() )
115  {
116  mFetchingSvg = QStringLiteral( "<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
117  }
118 
120 }
121 
122 QImage QgsSvgCache::svgAsImage( const QString &file, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
123  double widthScaleFactor, bool &fitsInCache, double fixedAspectRatio )
124 {
125  QMutexLocker locker( &mMutex );
126 
127  fitsInCache = true;
128  QgsSvgCacheEntry *currentEntry = cacheEntry( file, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio );
129 
130  QImage result;
131 
132  //if current entry image is 0: cache image for entry
133  // checks to see if image will fit into cache
134  //update stats for memory usage
135  if ( !currentEntry->image )
136  {
137  QSvgRenderer r( currentEntry->svgContent );
138  double hwRatio = 1.0;
139  if ( r.viewBoxF().width() > 0 )
140  {
141  if ( currentEntry->fixedAspectRatio > 0 )
142  {
143  hwRatio = currentEntry->fixedAspectRatio;
144  }
145  else
146  {
147  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
148  }
149  }
150  long cachedDataSize = 0;
151  cachedDataSize += currentEntry->svgContent.size();
152  cachedDataSize += static_cast< int >( currentEntry->size * currentEntry->size * hwRatio * 32 );
153  if ( cachedDataSize > mMaxCacheSize / 2 )
154  {
155  fitsInCache = false;
156  currentEntry->image.reset();
157 
158  // instead cache picture
159  if ( !currentEntry->picture )
160  {
161  cachePicture( currentEntry, false );
162  }
163 
164  // ...and render cached picture to result image
165  result = imageFromCachedPicture( *currentEntry );
166  }
167  else
168  {
169  cacheImage( currentEntry );
170  result = *( currentEntry->image );
171  }
173  }
174  else
175  {
176  result = *( currentEntry->image );
177  }
178 
179  return result;
180 }
181 
182 QPicture QgsSvgCache::svgAsPicture( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
183  double widthScaleFactor, bool forceVectorOutput, double fixedAspectRatio )
184 {
185  QMutexLocker locker( &mMutex );
186 
187  QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio );
188 
189  //if current entry picture is 0: cache picture for entry
190  //update stats for memory usage
191  if ( !currentEntry->picture )
192  {
193  cachePicture( currentEntry, forceVectorOutput );
195  }
196 
197  QPicture p;
198  // For some reason p.detach() doesn't seem to always work as intended, at
199  // least with QT 5.5 on Ubuntu 16.04
200  // Serialization/deserialization is a safe way to be ensured we don't
201  // share a copy.
202  p.setData( currentEntry->picture->data(), currentEntry->picture->size() );
203  return p;
204 }
205 
206 QByteArray QgsSvgCache::svgContent( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
207  double widthScaleFactor, double fixedAspectRatio )
208 {
209  QMutexLocker locker( &mMutex );
210 
211  QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio );
212 
213  return currentEntry->svgContent;
214 }
215 
216 QSizeF QgsSvgCache::svgViewboxSize( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio )
217 {
218  QMutexLocker locker( &mMutex );
219 
220  QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio );
221  return currentEntry->viewboxSize;
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( getContent( path, mMissingSvg, mFetchingSvg ) ) )
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 
282 void QgsSvgCache::replaceParamsAndCacheSvg( QgsSvgCacheEntry *entry )
283 {
284  if ( !entry )
285  {
286  return;
287  }
288 
289  QDomDocument svgDoc;
290  if ( !svgDoc.setContent( getContent( entry->path, mMissingSvg, mFetchingSvg ) ) )
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 
368 QByteArray QgsSvgCache::getImageData( const QString &path ) const
369 {
370  return getContent( path, mMissingSvg, mFetchingSvg );
371 };
372 
373 bool QgsSvgCache::checkReply( QNetworkReply *reply, const QString &path ) const
374 {
375  // we accept both real SVG mime types AND plain text types - because some sites
376  // (notably github) serve up svgs as raw text
377  QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
378  if ( !contentType.startsWith( QLatin1String( "image/svg+xml" ), Qt::CaseInsensitive )
379  && !contentType.startsWith( QLatin1String( "text/plain" ), Qt::CaseInsensitive ) )
380  {
381  QgsMessageLog::logMessage( tr( "Unexpected MIME type %1 received for %2" ).arg( contentType, path ), tr( "SVG" ) );
382  return false;
383  }
384  return true;
385 }
386 
387 void QgsSvgCache::cacheImage( QgsSvgCacheEntry *entry )
388 {
389  if ( !entry )
390  {
391  return;
392  }
393 
394  entry->image.reset();
395 
396  QSizeF viewBoxSize;
397  QSizeF scaledSize;
398  QSize imageSize = sizeForImage( *entry, viewBoxSize, scaledSize );
399 
400  // cast double image sizes to int for QImage
401  std::unique_ptr< QImage > image = qgis::make_unique< QImage >( imageSize, QImage::Format_ARGB32_Premultiplied );
402  image->fill( 0 ); // transparent background
403 
404  QPainter p( image.get() );
405  QSvgRenderer r( entry->svgContent );
406  if ( qgsDoubleNear( viewBoxSize.width(), viewBoxSize.height() ) )
407  {
408  r.render( &p );
409  }
410  else
411  {
412  QSizeF s( viewBoxSize );
413  s.scale( scaledSize.width(), scaledSize.height(), Qt::KeepAspectRatio );
414  QRectF rect( ( imageSize.width() - s.width() ) / 2, ( imageSize.height() - s.height() ) / 2, s.width(), s.height() );
415  r.render( &p, rect );
416  }
417 
418  mTotalSize += ( image->width() * image->height() * 32 );
419  entry->image = std::move( image );
420 }
421 
422 void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry, bool forceVectorOutput )
423 {
424  Q_UNUSED( forceVectorOutput );
425  if ( !entry )
426  {
427  return;
428  }
429 
430  entry->picture.reset();
431 
432  bool isFixedAR = entry->fixedAspectRatio > 0;
433 
434  //correct QPictures dpi correction
435  std::unique_ptr< QPicture > picture = qgis::make_unique< QPicture >();
436  QRectF rect;
437  QSvgRenderer r( entry->svgContent );
438  double hwRatio = 1.0;
439  if ( r.viewBoxF().width() > 0 )
440  {
441  if ( isFixedAR )
442  {
443  hwRatio = entry->fixedAspectRatio;
444  }
445  else
446  {
447  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
448  }
449  }
450 
451  double wSize = entry->size;
452  double hSize = wSize * hwRatio;
453 
454  QSizeF s( r.viewBoxF().size() );
455  s.scale( wSize, hSize, isFixedAR ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio );
456  rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
457 
458  QPainter p( picture.get() );
459  r.render( &p, rect );
460  entry->picture = std::move( picture );
461  mTotalSize += entry->picture->size();
462 }
463 
464 QgsSvgCacheEntry *QgsSvgCache::cacheEntry( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
465  double widthScaleFactor, double fixedAspectRatio )
466 {
467  QgsSvgCacheEntry *currentEntry = findExistingEntry( new QgsSvgCacheEntry( path, size, strokeWidth, widthScaleFactor, fill, stroke, fixedAspectRatio ) );
468 
469  if ( currentEntry->svgContent.isEmpty() )
470  {
471  replaceParamsAndCacheSvg( currentEntry );
472  }
473 
474  return currentEntry;
475 }
476 
477 
478 void QgsSvgCache::replaceElemParams( QDomElement &elem, const QColor &fill, const QColor &stroke, double strokeWidth )
479 {
480  if ( elem.isNull() )
481  {
482  return;
483  }
484 
485  //go through attributes
486  QDomNamedNodeMap attributes = elem.attributes();
487  int nAttributes = attributes.count();
488  for ( int i = 0; i < nAttributes; ++i )
489  {
490  QDomAttr attribute = attributes.item( i ).toAttr();
491  //e.g. style="fill:param(fill);param(stroke)"
492  if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
493  {
494  //entries separated by ';'
495  QString newAttributeString;
496 
497  QStringList entryList = attribute.value().split( ';' );
498  QStringList::const_iterator entryIt = entryList.constBegin();
499  for ( ; entryIt != entryList.constEnd(); ++entryIt )
500  {
501  QStringList keyValueSplit = entryIt->split( ':' );
502  if ( keyValueSplit.size() < 2 )
503  {
504  continue;
505  }
506  QString key = keyValueSplit.at( 0 );
507  QString value = keyValueSplit.at( 1 );
508  if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
509  {
510  value = fill.name();
511  }
512  else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
513  {
514  value = fill.alphaF();
515  }
516  else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
517  {
518  value = stroke.name();
519  }
520  else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
521  {
522  value = stroke.alphaF();
523  }
524  else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
525  {
526  value = QString::number( strokeWidth );
527  }
528 
529  if ( entryIt != entryList.constBegin() )
530  {
531  newAttributeString.append( ';' );
532  }
533  newAttributeString.append( key + ':' + value );
534  }
535  elem.setAttribute( attribute.name(), newAttributeString );
536  }
537  else
538  {
539  QString value = attribute.value();
540  if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
541  {
542  elem.setAttribute( attribute.name(), fill.name() );
543  }
544  else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
545  {
546  elem.setAttribute( attribute.name(), fill.alphaF() );
547  }
548  else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
549  {
550  elem.setAttribute( attribute.name(), stroke.name() );
551  }
552  else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
553  {
554  elem.setAttribute( attribute.name(), stroke.alphaF() );
555  }
556  else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
557  {
558  elem.setAttribute( attribute.name(), QString::number( strokeWidth ) );
559  }
560  }
561  }
562 
563  QDomNodeList childList = elem.childNodes();
564  int nChildren = childList.count();
565  for ( int i = 0; i < nChildren; ++i )
566  {
567  QDomElement childElem = childList.at( i ).toElement();
568  replaceElemParams( childElem, fill, stroke, strokeWidth );
569  }
570 }
571 
572 void QgsSvgCache::containsElemParams( const QDomElement &elem, bool &hasFillParam, bool &hasDefaultFill, QColor &defaultFill,
573  bool &hasFillOpacityParam, bool &hasDefaultFillOpacity, double &defaultFillOpacity,
574  bool &hasStrokeParam, bool &hasDefaultStroke, QColor &defaultStroke,
575  bool &hasStrokeWidthParam, bool &hasDefaultStrokeWidth, double &defaultStrokeWidth,
576  bool &hasStrokeOpacityParam, bool &hasDefaultStrokeOpacity, double &defaultStrokeOpacity ) const
577 {
578  if ( elem.isNull() )
579  {
580  return;
581  }
582 
583  //we already have all the information, no need to go deeper
584  if ( hasFillParam && hasStrokeParam && hasStrokeWidthParam && hasFillOpacityParam && hasStrokeOpacityParam )
585  {
586  return;
587  }
588 
589  //check this elements attribute
590  QDomNamedNodeMap attributes = elem.attributes();
591  int nAttributes = attributes.count();
592 
593  QStringList valueSplit;
594  for ( int i = 0; i < nAttributes; ++i )
595  {
596  QDomAttr attribute = attributes.item( i ).toAttr();
597  if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
598  {
599  //entries separated by ';'
600  QStringList entryList = attribute.value().split( ';' );
601  QStringList::const_iterator entryIt = entryList.constBegin();
602  for ( ; entryIt != entryList.constEnd(); ++entryIt )
603  {
604  QStringList keyValueSplit = entryIt->split( ':' );
605  if ( keyValueSplit.size() < 2 )
606  {
607  continue;
608  }
609  QString value = keyValueSplit.at( 1 );
610  valueSplit = value.split( ' ' );
611  if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
612  {
613  hasFillParam = true;
614  if ( valueSplit.size() > 1 )
615  {
616  defaultFill = QColor( valueSplit.at( 1 ) );
617  hasDefaultFill = true;
618  }
619  }
620  else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
621  {
622  hasFillOpacityParam = true;
623  if ( valueSplit.size() > 1 )
624  {
625  bool ok;
626  double opacity = valueSplit.at( 1 ).toDouble( &ok );
627  if ( ok )
628  {
629  defaultFillOpacity = opacity;
630  hasDefaultFillOpacity = true;
631  }
632  }
633  }
634  else if ( !hasStrokeParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
635  {
636  hasStrokeParam = true;
637  if ( valueSplit.size() > 1 )
638  {
639  defaultStroke = QColor( valueSplit.at( 1 ) );
640  hasDefaultStroke = true;
641  }
642  }
643  else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
644  {
645  hasStrokeWidthParam = true;
646  if ( valueSplit.size() > 1 )
647  {
648  defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
649  hasDefaultStrokeWidth = true;
650  }
651  }
652  else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
653  {
654  hasStrokeOpacityParam = true;
655  if ( valueSplit.size() > 1 )
656  {
657  bool ok;
658  double opacity = valueSplit.at( 1 ).toDouble( &ok );
659  if ( ok )
660  {
661  defaultStrokeOpacity = opacity;
662  hasDefaultStrokeOpacity = true;
663  }
664  }
665  }
666  }
667  }
668  else
669  {
670  QString value = attribute.value();
671  valueSplit = value.split( ' ' );
672  if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
673  {
674  hasFillParam = true;
675  if ( valueSplit.size() > 1 )
676  {
677  defaultFill = QColor( valueSplit.at( 1 ) );
678  hasDefaultFill = true;
679  }
680  }
681  else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
682  {
683  hasFillOpacityParam = true;
684  if ( valueSplit.size() > 1 )
685  {
686  bool ok;
687  double opacity = valueSplit.at( 1 ).toDouble( &ok );
688  if ( ok )
689  {
690  defaultFillOpacity = opacity;
691  hasDefaultFillOpacity = true;
692  }
693  }
694  }
695  else if ( !hasStrokeParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
696  {
697  hasStrokeParam = true;
698  if ( valueSplit.size() > 1 )
699  {
700  defaultStroke = QColor( valueSplit.at( 1 ) );
701  hasDefaultStroke = true;
702  }
703  }
704  else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
705  {
706  hasStrokeWidthParam = true;
707  if ( valueSplit.size() > 1 )
708  {
709  defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
710  hasDefaultStrokeWidth = true;
711  }
712  }
713  else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
714  {
715  hasStrokeOpacityParam = true;
716  if ( valueSplit.size() > 1 )
717  {
718  bool ok;
719  double opacity = valueSplit.at( 1 ).toDouble( &ok );
720  if ( ok )
721  {
722  defaultStrokeOpacity = opacity;
723  hasDefaultStrokeOpacity = true;
724  }
725  }
726  }
727  }
728  }
729 
730  //pass it further to child items
731  QDomNodeList childList = elem.childNodes();
732  int nChildren = childList.count();
733  for ( int i = 0; i < nChildren; ++i )
734  {
735  QDomElement childElem = childList.at( i ).toElement();
736  containsElemParams( childElem, hasFillParam, hasDefaultFill, defaultFill,
737  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
738  hasStrokeParam, hasDefaultStroke, defaultStroke,
739  hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
740  hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
741  }
742 }
743 
744 QSize QgsSvgCache::sizeForImage( const QgsSvgCacheEntry &entry, QSizeF &viewBoxSize, QSizeF &scaledSize ) const
745 {
746  bool isFixedAR = entry.fixedAspectRatio > 0;
747 
748  QSvgRenderer r( entry.svgContent );
749  double hwRatio = 1.0;
750  viewBoxSize = r.viewBoxF().size();
751  if ( viewBoxSize.width() > 0 )
752  {
753  if ( isFixedAR )
754  {
755  hwRatio = entry.fixedAspectRatio;
756  }
757  else
758  {
759  hwRatio = viewBoxSize.height() / viewBoxSize.width();
760  }
761  }
762 
763  // cast double image sizes to int for QImage
764  scaledSize.setWidth( entry.size );
765  int wImgSize = static_cast< int >( scaledSize.width() );
766  if ( wImgSize < 1 )
767  {
768  wImgSize = 1;
769  }
770  scaledSize.setHeight( scaledSize.width() * hwRatio );
771  int hImgSize = static_cast< int >( scaledSize.height() );
772  if ( hImgSize < 1 )
773  {
774  hImgSize = 1;
775  }
776  return QSize( wImgSize, hImgSize );
777 }
778 
779 QImage QgsSvgCache::imageFromCachedPicture( const QgsSvgCacheEntry &entry ) const
780 {
781  QSizeF viewBoxSize;
782  QSizeF scaledSize;
783  QImage image( sizeForImage( entry, viewBoxSize, scaledSize ), QImage::Format_ARGB32_Premultiplied );
784  image.fill( 0 ); // transparent background
785 
786  QPainter p( &image );
787  p.drawPicture( QPoint( 0, 0 ), *entry.picture );
788  return image;
789 }
790 
QPicture svgAsPicture(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool forceVectorOutput=false, double fixedAspectRatio=0)
Gets SVG as QPicture&.
Abstract base class for file content caches, such as SVG or raster image caches.
QByteArray getImageData(const QString &path) const
Gets image data.
static QString defaultThemePath()
Returns the path to the default theme directory.
QImage svgAsImage(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool &fitsInCache, double fixedAspectRatio=0)
Gets SVG as QImage.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:265
Base class for entries in a QgsAbstractContentCache.
void remoteContentFetched(const QString &url)
Emitted when the cache has finished retrieving content from a remote url.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
QgsSvgCache(QObject *parent=nullptr)
Constructor for QgsSvgCache.
Definition: qgssvgcache.cpp:99
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.
QgsSvgCacheEntry * findExistingEntry(QgsSvgCacheEntry *entryTemplate)
Returns the existing entry from the cache which matches entryTemplate (deleting entryTemplate when do...
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0)
Gets SVG content.
void remoteSvgFetched(const QString &url)
Emitted when the cache has finished retrieving an SVG file from a remote url.
QByteArray getContent(const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent) const
Gets the file content corresponding to the given path.
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.
long mTotalSize
Estimated total size of all cached content.
void trimToMaximumSize()
Removes the least used cache entries until the maximum cache size is under the predefined size limit...
bool checkReply(QNetworkReply *reply, const QString &path) const override
Runs additional checks on a network reply to ensure that the reply content is consistent with that re...