QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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, bool blocking )
124 {
125  QMutexLocker locker( &mMutex );
126 
127  fitsInCache = true;
128  QgsSvgCacheEntry *currentEntry = cacheEntry( file, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking );
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, bool blocking )
184 {
185  QMutexLocker locker( &mMutex );
186 
187  QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking );
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, bool blocking )
208 {
209  QMutexLocker locker( &mMutex );
210 
211  QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking );
212 
213  return currentEntry->svgContent;
214 }
215 
216 QSizeF QgsSvgCache::svgViewboxSize( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
217  double widthScaleFactor, double fixedAspectRatio, bool blocking )
218 {
219  QMutexLocker locker( &mMutex );
220 
221  QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking );
222  return currentEntry->viewboxSize;
223 }
224 
225 void QgsSvgCache::containsParams( const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasStrokeParam, QColor &defaultStrokeColor,
226  bool &hasStrokeWidthParam, double &defaultStrokeWidth, bool blocking ) const
227 {
228  bool hasDefaultFillColor = false;
229  bool hasFillOpacityParam = false;
230  bool hasDefaultFillOpacity = false;
231  double defaultFillOpacity = 1.0;
232  bool hasDefaultStrokeColor = false;
233  bool hasDefaultStrokeWidth = false;
234  bool hasStrokeOpacityParam = false;
235  bool hasDefaultStrokeOpacity = false;
236  double defaultStrokeOpacity = 1.0;
237 
238  containsParams( path, hasFillParam, hasDefaultFillColor, defaultFillColor,
239  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
240  hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
241  hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
242  hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity,
243  blocking );
244 }
245 
246 void QgsSvgCache::containsParams( const QString &path,
247  bool &hasFillParam, bool &hasDefaultFillParam, QColor &defaultFillColor,
248  bool &hasFillOpacityParam, bool &hasDefaultFillOpacity, double &defaultFillOpacity,
249  bool &hasStrokeParam, bool &hasDefaultStrokeColor, QColor &defaultStrokeColor,
250  bool &hasStrokeWidthParam, bool &hasDefaultStrokeWidth, double &defaultStrokeWidth,
251  bool &hasStrokeOpacityParam, bool &hasDefaultStrokeOpacity, double &defaultStrokeOpacity,
252  bool blocking ) const
253 {
254  hasFillParam = false;
255  hasFillOpacityParam = false;
256  hasStrokeParam = false;
257  hasStrokeWidthParam = false;
258  hasStrokeOpacityParam = false;
259  defaultFillColor = QColor( Qt::white );
260  defaultFillOpacity = 1.0;
261  defaultStrokeColor = QColor( Qt::black );
262  defaultStrokeWidth = 0.2;
263  defaultStrokeOpacity = 1.0;
264 
265  hasDefaultFillParam = false;
266  hasDefaultFillOpacity = false;
267  hasDefaultStrokeColor = false;
268  hasDefaultStrokeWidth = false;
269  hasDefaultStrokeOpacity = false;
270 
271  QDomDocument svgDoc;
272  if ( !svgDoc.setContent( getContent( path, mMissingSvg, mFetchingSvg, blocking ) ) )
273  {
274  return;
275  }
276 
277  QDomElement docElem = svgDoc.documentElement();
278  containsElemParams( docElem, hasFillParam, hasDefaultFillParam, defaultFillColor,
279  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
280  hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
281  hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
282  hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
283 }
284 
285 void QgsSvgCache::replaceParamsAndCacheSvg( QgsSvgCacheEntry *entry, bool blocking )
286 {
287  if ( !entry )
288  {
289  return;
290  }
291 
292  QDomDocument svgDoc;
293  if ( !svgDoc.setContent( getContent( entry->path, mMissingSvg, mFetchingSvg, blocking ) ) )
294  {
295  return;
296  }
297 
298  //replace fill color, stroke color, stroke with in all nodes
299  QDomElement docElem = svgDoc.documentElement();
300 
301  QSizeF viewboxSize;
302  double sizeScaleFactor = calcSizeScaleFactor( entry, docElem, viewboxSize );
303  entry->viewboxSize = viewboxSize;
304  replaceElemParams( docElem, entry->fill, entry->stroke, entry->strokeWidth * sizeScaleFactor );
305 
306  entry->svgContent = svgDoc.toByteArray( 0 );
307 
308  // toByteArray screws up tspans inside text by adding new lines before and after each span... this should help, at the
309  // risk of potentially breaking some svgs where the newline is desired
310  entry->svgContent.replace( "\n<tspan", "<tspan" );
311  entry->svgContent.replace( "</tspan>\n", "</tspan>" );
312 
313  mTotalSize += entry->svgContent.size();
314 }
315 
316 double QgsSvgCache::calcSizeScaleFactor( QgsSvgCacheEntry *entry, const QDomElement &docElem, QSizeF &viewboxSize ) const
317 {
318  QString viewBox;
319 
320  //bad size
321  if ( !entry || qgsDoubleNear( entry->size, 0.0 ) )
322  return 1.0;
323 
324  //find svg viewbox attribute
325  //first check if docElem is svg element
326  if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "viewBox" ) ) )
327  {
328  viewBox = docElem.attribute( QStringLiteral( "viewBox" ), QString() );
329  }
330  else if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "viewbox" ) ) )
331  {
332  viewBox = docElem.attribute( QStringLiteral( "viewbox" ), QString() );
333  }
334  else
335  {
336  QDomElement svgElem = docElem.firstChildElement( QStringLiteral( "svg" ) );
337  if ( !svgElem.isNull() )
338  {
339  if ( svgElem.hasAttribute( QStringLiteral( "viewBox" ) ) )
340  viewBox = svgElem.attribute( QStringLiteral( "viewBox" ), QString() );
341  else if ( svgElem.hasAttribute( QStringLiteral( "viewbox" ) ) )
342  viewBox = svgElem.attribute( QStringLiteral( "viewbox" ), QString() );
343  }
344  }
345 
346  //could not find valid viewbox attribute
347  if ( viewBox.isEmpty() )
348  return 1.0;
349 
350  //width should be 3rd element in a 4 part space delimited string
351  QStringList parts = viewBox.split( ' ' );
352  if ( parts.count() != 4 )
353  return 1.0;
354 
355  bool heightOk = false;
356  double height = parts.at( 3 ).toDouble( &heightOk );
357 
358  bool widthOk = false;
359  double width = parts.at( 2 ).toDouble( &widthOk );
360  if ( widthOk )
361  {
362  if ( heightOk )
363  viewboxSize = QSizeF( width, height );
364  return width / entry->size;
365  }
366 
367  return 1.0;
368 }
369 
370 
371 QByteArray QgsSvgCache::getImageData( const QString &path, bool blocking ) const
372 {
373  return getContent( path, mMissingSvg, mFetchingSvg, blocking );
374 };
375 
376 bool QgsSvgCache::checkReply( QNetworkReply *reply, const QString &path ) const
377 {
378  // we accept both real SVG mime types AND plain text types - because some sites
379  // (notably github) serve up svgs as raw text
380  QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
381  if ( !contentType.startsWith( QLatin1String( "image/svg+xml" ), Qt::CaseInsensitive )
382  && !contentType.startsWith( QLatin1String( "text/plain" ), Qt::CaseInsensitive ) )
383  {
384  QgsMessageLog::logMessage( tr( "Unexpected MIME type %1 received for %2" ).arg( contentType, path ), tr( "SVG" ) );
385  return false;
386  }
387  return true;
388 }
389 
390 void QgsSvgCache::cacheImage( QgsSvgCacheEntry *entry )
391 {
392  if ( !entry )
393  {
394  return;
395  }
396 
397  entry->image.reset();
398 
399  QSizeF viewBoxSize;
400  QSizeF scaledSize;
401  QSize imageSize = sizeForImage( *entry, viewBoxSize, scaledSize );
402 
403  // cast double image sizes to int for QImage
404  std::unique_ptr< QImage > image = qgis::make_unique< QImage >( imageSize, QImage::Format_ARGB32_Premultiplied );
405  image->fill( 0 ); // transparent background
406 
407  QPainter p( image.get() );
408  QSvgRenderer r( entry->svgContent );
409  if ( qgsDoubleNear( viewBoxSize.width(), viewBoxSize.height() ) )
410  {
411  r.render( &p );
412  }
413  else
414  {
415  QSizeF s( viewBoxSize );
416  s.scale( scaledSize.width(), scaledSize.height(), Qt::KeepAspectRatio );
417  QRectF rect( ( imageSize.width() - s.width() ) / 2, ( imageSize.height() - s.height() ) / 2, s.width(), s.height() );
418  r.render( &p, rect );
419  }
420 
421  mTotalSize += ( image->width() * image->height() * 32 );
422  entry->image = std::move( image );
423 }
424 
425 void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry, bool forceVectorOutput )
426 {
427  Q_UNUSED( forceVectorOutput )
428  if ( !entry )
429  {
430  return;
431  }
432 
433  entry->picture.reset();
434 
435  bool isFixedAR = entry->fixedAspectRatio > 0;
436 
437  //correct QPictures dpi correction
438  std::unique_ptr< QPicture > picture = qgis::make_unique< QPicture >();
439  QRectF rect;
440  QSvgRenderer r( entry->svgContent );
441  double hwRatio = 1.0;
442  if ( r.viewBoxF().width() > 0 )
443  {
444  if ( isFixedAR )
445  {
446  hwRatio = entry->fixedAspectRatio;
447  }
448  else
449  {
450  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
451  }
452  }
453 
454  double wSize = entry->size;
455  double hSize = wSize * hwRatio;
456 
457  QSizeF s( r.viewBoxF().size() );
458  s.scale( wSize, hSize, isFixedAR ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio );
459  rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
460 
461  QPainter p( picture.get() );
462  r.render( &p, rect );
463  entry->picture = std::move( picture );
464  mTotalSize += entry->picture->size();
465 }
466 
467 QgsSvgCacheEntry *QgsSvgCache::cacheEntry( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
468  double widthScaleFactor, double fixedAspectRatio, bool blocking )
469 {
470  QgsSvgCacheEntry *currentEntry = findExistingEntry( new QgsSvgCacheEntry( path, size, strokeWidth, widthScaleFactor, fill, stroke, fixedAspectRatio ) );
471 
472  if ( currentEntry->svgContent.isEmpty() )
473  {
474  replaceParamsAndCacheSvg( currentEntry, blocking );
475  }
476 
477  return currentEntry;
478 }
479 
480 
481 void QgsSvgCache::replaceElemParams( QDomElement &elem, const QColor &fill, const QColor &stroke, double strokeWidth )
482 {
483  if ( elem.isNull() )
484  {
485  return;
486  }
487 
488  //go through attributes
489  QDomNamedNodeMap attributes = elem.attributes();
490  int nAttributes = attributes.count();
491  for ( int i = 0; i < nAttributes; ++i )
492  {
493  QDomAttr attribute = attributes.item( i ).toAttr();
494  //e.g. style="fill:param(fill);param(stroke)"
495  if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
496  {
497  //entries separated by ';'
498  QString newAttributeString;
499 
500  QStringList entryList = attribute.value().split( ';' );
501  QStringList::const_iterator entryIt = entryList.constBegin();
502  for ( ; entryIt != entryList.constEnd(); ++entryIt )
503  {
504  QStringList keyValueSplit = entryIt->split( ':' );
505  if ( keyValueSplit.size() < 2 )
506  {
507  continue;
508  }
509  const QString key = keyValueSplit.at( 0 );
510  QString value = keyValueSplit.at( 1 );
511  QString newValue = value;
512  value = value.trimmed().toLower();
513 
514  if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
515  {
516  newValue = fill.name();
517  }
518  else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
519  {
520  newValue = QString::number( fill.alphaF() );
521  }
522  else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
523  {
524  newValue = stroke.name();
525  }
526  else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
527  {
528  newValue = QString::number( stroke.alphaF() );
529  }
530  else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
531  {
532  newValue = QString::number( strokeWidth );
533  }
534 
535  if ( entryIt != entryList.constBegin() )
536  {
537  newAttributeString.append( ';' );
538  }
539  newAttributeString.append( key + ':' + newValue );
540  }
541  elem.setAttribute( attribute.name(), newAttributeString );
542  }
543  else
544  {
545  const QString value = attribute.value().trimmed().toLower();
546  if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
547  {
548  elem.setAttribute( attribute.name(), fill.name() );
549  }
550  else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
551  {
552  elem.setAttribute( attribute.name(), fill.alphaF() );
553  }
554  else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
555  {
556  elem.setAttribute( attribute.name(), stroke.name() );
557  }
558  else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
559  {
560  elem.setAttribute( attribute.name(), stroke.alphaF() );
561  }
562  else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
563  {
564  elem.setAttribute( attribute.name(), QString::number( strokeWidth ) );
565  }
566  }
567  }
568 
569  QDomNodeList childList = elem.childNodes();
570  int nChildren = childList.count();
571  for ( int i = 0; i < nChildren; ++i )
572  {
573  QDomElement childElem = childList.at( i ).toElement();
574  replaceElemParams( childElem, fill, stroke, strokeWidth );
575  }
576 }
577 
578 void QgsSvgCache::containsElemParams( const QDomElement &elem, bool &hasFillParam, bool &hasDefaultFill, QColor &defaultFill,
579  bool &hasFillOpacityParam, bool &hasDefaultFillOpacity, double &defaultFillOpacity,
580  bool &hasStrokeParam, bool &hasDefaultStroke, QColor &defaultStroke,
581  bool &hasStrokeWidthParam, bool &hasDefaultStrokeWidth, double &defaultStrokeWidth,
582  bool &hasStrokeOpacityParam, bool &hasDefaultStrokeOpacity, double &defaultStrokeOpacity ) const
583 {
584  if ( elem.isNull() )
585  {
586  return;
587  }
588 
589  //we already have all the information, no need to go deeper
590  if ( hasFillParam && hasStrokeParam && hasStrokeWidthParam && hasFillOpacityParam && hasStrokeOpacityParam )
591  {
592  return;
593  }
594 
595  //check this elements attribute
596  QDomNamedNodeMap attributes = elem.attributes();
597  int nAttributes = attributes.count();
598 
599  QStringList valueSplit;
600  for ( int i = 0; i < nAttributes; ++i )
601  {
602  QDomAttr attribute = attributes.item( i ).toAttr();
603  if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
604  {
605  //entries separated by ';'
606  QStringList entryList = attribute.value().split( ';' );
607  QStringList::const_iterator entryIt = entryList.constBegin();
608  for ( ; entryIt != entryList.constEnd(); ++entryIt )
609  {
610  QStringList keyValueSplit = entryIt->split( ':' );
611  if ( keyValueSplit.size() < 2 )
612  {
613  continue;
614  }
615  QString value = keyValueSplit.at( 1 );
616  valueSplit = value.split( ' ' );
617  if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
618  {
619  hasFillParam = true;
620  if ( valueSplit.size() > 1 )
621  {
622  defaultFill = QColor( valueSplit.at( 1 ) );
623  hasDefaultFill = true;
624  }
625  }
626  else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
627  {
628  hasFillOpacityParam = true;
629  if ( valueSplit.size() > 1 )
630  {
631  bool ok;
632  double opacity = valueSplit.at( 1 ).toDouble( &ok );
633  if ( ok )
634  {
635  defaultFillOpacity = opacity;
636  hasDefaultFillOpacity = true;
637  }
638  }
639  }
640  else if ( !hasStrokeParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
641  {
642  hasStrokeParam = true;
643  if ( valueSplit.size() > 1 )
644  {
645  defaultStroke = QColor( valueSplit.at( 1 ) );
646  hasDefaultStroke = true;
647  }
648  }
649  else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
650  {
651  hasStrokeWidthParam = true;
652  if ( valueSplit.size() > 1 )
653  {
654  defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
655  hasDefaultStrokeWidth = true;
656  }
657  }
658  else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
659  {
660  hasStrokeOpacityParam = true;
661  if ( valueSplit.size() > 1 )
662  {
663  bool ok;
664  double opacity = valueSplit.at( 1 ).toDouble( &ok );
665  if ( ok )
666  {
667  defaultStrokeOpacity = opacity;
668  hasDefaultStrokeOpacity = true;
669  }
670  }
671  }
672  }
673  }
674  else
675  {
676  QString value = attribute.value();
677  valueSplit = value.split( ' ' );
678  if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
679  {
680  hasFillParam = true;
681  if ( valueSplit.size() > 1 )
682  {
683  defaultFill = QColor( valueSplit.at( 1 ) );
684  hasDefaultFill = true;
685  }
686  }
687  else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
688  {
689  hasFillOpacityParam = true;
690  if ( valueSplit.size() > 1 )
691  {
692  bool ok;
693  double opacity = valueSplit.at( 1 ).toDouble( &ok );
694  if ( ok )
695  {
696  defaultFillOpacity = opacity;
697  hasDefaultFillOpacity = true;
698  }
699  }
700  }
701  else if ( !hasStrokeParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
702  {
703  hasStrokeParam = true;
704  if ( valueSplit.size() > 1 )
705  {
706  defaultStroke = QColor( valueSplit.at( 1 ) );
707  hasDefaultStroke = true;
708  }
709  }
710  else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
711  {
712  hasStrokeWidthParam = true;
713  if ( valueSplit.size() > 1 )
714  {
715  defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
716  hasDefaultStrokeWidth = true;
717  }
718  }
719  else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
720  {
721  hasStrokeOpacityParam = true;
722  if ( valueSplit.size() > 1 )
723  {
724  bool ok;
725  double opacity = valueSplit.at( 1 ).toDouble( &ok );
726  if ( ok )
727  {
728  defaultStrokeOpacity = opacity;
729  hasDefaultStrokeOpacity = true;
730  }
731  }
732  }
733  }
734  }
735 
736  //pass it further to child items
737  QDomNodeList childList = elem.childNodes();
738  int nChildren = childList.count();
739  for ( int i = 0; i < nChildren; ++i )
740  {
741  QDomElement childElem = childList.at( i ).toElement();
742  containsElemParams( childElem, hasFillParam, hasDefaultFill, defaultFill,
743  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
744  hasStrokeParam, hasDefaultStroke, defaultStroke,
745  hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
746  hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
747  }
748 }
749 
750 QSize QgsSvgCache::sizeForImage( const QgsSvgCacheEntry &entry, QSizeF &viewBoxSize, QSizeF &scaledSize ) const
751 {
752  bool isFixedAR = entry.fixedAspectRatio > 0;
753 
754  QSvgRenderer r( entry.svgContent );
755  double hwRatio = 1.0;
756  viewBoxSize = r.viewBoxF().size();
757  if ( viewBoxSize.width() > 0 )
758  {
759  if ( isFixedAR )
760  {
761  hwRatio = entry.fixedAspectRatio;
762  }
763  else
764  {
765  hwRatio = viewBoxSize.height() / viewBoxSize.width();
766  }
767  }
768 
769  // cast double image sizes to int for QImage
770  scaledSize.setWidth( entry.size );
771  int wImgSize = static_cast< int >( scaledSize.width() );
772  if ( wImgSize < 1 )
773  {
774  wImgSize = 1;
775  }
776  scaledSize.setHeight( scaledSize.width() * hwRatio );
777  int hImgSize = static_cast< int >( scaledSize.height() );
778  if ( hImgSize < 1 )
779  {
780  hImgSize = 1;
781  }
782  return QSize( wImgSize, hImgSize );
783 }
784 
785 QImage QgsSvgCache::imageFromCachedPicture( const QgsSvgCacheEntry &entry ) const
786 {
787  QSizeF viewBoxSize;
788  QSizeF scaledSize;
789  QImage image( sizeForImage( entry, viewBoxSize, scaledSize ), QImage::Format_ARGB32_Premultiplied );
790  image.fill( 0 ); // transparent background
791 
792  QPainter p( &image );
793  p.drawPicture( QPoint( 0, 0 ), *entry.picture );
794  return image;
795 }
796 
QByteArray getContent(const QString &path, const QByteArray &missingContent, const QByteArray &fetchingContent, bool blocking=false) const
Gets the file content corresponding to the given path.
Abstract base class for file content caches, such as SVG or raster image caches.
static QString defaultThemePath()
Returns the path to the default theme directory.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QImage svgAsImage(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool &fitsInCache, double fixedAspectRatio=0, bool blocking=false)
Gets SVG as QImage.
void containsParams(const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasStrokeParam, QColor &defaultStrokeColor, bool &hasStrokeWidthParam, double &defaultStrokeWidth, bool blocking=false) const
Tests if an svg file contains parameters for fill, stroke color, stroke width.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:280
Base class for entries in a QgsAbstractContentCache.
void remoteContentFetched(const QString &url)
Emitted when the cache has finished retrieving content from a remote url.
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0, bool blocking=false)
Gets SVG content.
QSizeF svgViewboxSize(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0, bool blocking=false)
Calculates the viewbox size of a (possibly cached) SVG file.
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).
QByteArray getImageData(const QString &path, bool blocking=false) const
Gets the SVG content corresponding to the given path.
QgsSvgCache(QObject *parent=nullptr)
Constructor for QgsSvgCache.
Definition: qgssvgcache.cpp:99
QgsSvgCacheEntry * findExistingEntry(QgsSvgCacheEntry *entryTemplate)
Returns the existing entry from the cache which matches entryTemplate (deleting entryTemplate when do...
void remoteSvgFetched(const QString &url)
Emitted when the cache has finished retrieving an SVG file from a remote url.
QPicture svgAsPicture(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool forceVectorOutput=false, double fixedAspectRatio=0, bool blocking=false)
Gets SVG as QPicture&.
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...