26 #include <QApplication>
27 #include <QCoreApplication>
29 #include <QDomDocument>
30 #include <QDomElement>
35 #include <QSvgRenderer>
37 #include <QNetworkReply>
38 #include <QNetworkRequest>
46 QgsSvgCacheEntry::QgsSvgCacheEntry(
const QString &path,
double size,
double strokeWidth,
double widthScaleFactor,
const QColor &fill,
const QColor &stroke,
double fixedAspectRatio )
49 , strokeWidth( strokeWidth )
50 , widthScaleFactor( widthScaleFactor )
51 , fixedAspectRatio( fixedAspectRatio )
59 const QgsSvgCacheEntry *otherSvg =
dynamic_cast< const QgsSvgCacheEntry *
>( other );
62 || !
qgsDoubleNear( otherSvg->fixedAspectRatio, fixedAspectRatio )
65 || !
qgsDoubleNear( otherSvg->widthScaleFactor, widthScaleFactor )
66 || otherSvg->fill != fill
67 || otherSvg->stroke != stroke
68 || otherSvg->path != path )
74 int QgsSvgCacheEntry::dataSize()
const
76 int size = svgContent.size();
79 size += picture->size();
83 size += ( image->width() * image->height() * 32 );
88 void QgsSvgCacheEntry::dump()
const
90 QgsDebugMsgLevel( QStringLiteral(
"path: %1, size %2, width scale factor %3" ).arg( path ).arg( size ).arg( widthScaleFactor ), 4 );
102 mMissingSvg = QStringLiteral(
"<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
105 if ( QFile::exists( downloadingSvgPath ) )
107 QFile file( downloadingSvgPath );
108 if ( file.open( QIODevice::ReadOnly ) )
110 mFetchingSvg = file.readAll();
114 if ( mFetchingSvg.isEmpty() )
116 mFetchingSvg = QStringLiteral(
"<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
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 )
125 QMutexLocker locker( &
mMutex );
128 QgsSvgCacheEntry *currentEntry = cacheEntry( file, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking );
135 if ( !currentEntry->image )
137 QSvgRenderer r( currentEntry->svgContent );
138 double hwRatio = 1.0;
139 if ( r.viewBoxF().width() > 0 )
141 if ( currentEntry->fixedAspectRatio > 0 )
143 hwRatio = currentEntry->fixedAspectRatio;
147 hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
150 long cachedDataSize = 0;
151 cachedDataSize += currentEntry->svgContent.size();
152 cachedDataSize +=
static_cast< int >( currentEntry->size * currentEntry->size * hwRatio * 32 );
156 currentEntry->image.reset();
159 if ( !currentEntry->picture )
161 cachePicture( currentEntry,
false );
165 result = imageFromCachedPicture( *currentEntry );
169 cacheImage( currentEntry );
170 result = *( currentEntry->image );
176 result = *( currentEntry->image );
183 double widthScaleFactor,
bool forceVectorOutput,
double fixedAspectRatio,
bool blocking )
185 QMutexLocker locker( &
mMutex );
187 QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking );
191 if ( !currentEntry->picture )
193 cachePicture( currentEntry, forceVectorOutput );
202 p.setData( currentEntry->picture->data(), currentEntry->picture->size() );
206 QByteArray
QgsSvgCache::svgContent(
const QString &path,
double size,
const QColor &fill,
const QColor &stroke,
double strokeWidth,
207 double widthScaleFactor,
double fixedAspectRatio,
bool blocking,
bool *isMissingImage )
209 QMutexLocker locker( &
mMutex );
211 QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking, isMissingImage );
213 return currentEntry->svgContent;
217 double widthScaleFactor,
double fixedAspectRatio,
bool blocking )
219 QMutexLocker locker( &
mMutex );
221 QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking );
222 return currentEntry->viewboxSize;
225 void QgsSvgCache::containsParams(
const QString &path,
bool &hasFillParam, QColor &defaultFillColor,
bool &hasStrokeParam, QColor &defaultStrokeColor,
226 bool &hasStrokeWidthParam,
double &defaultStrokeWidth,
bool blocking )
const
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;
238 containsParams( path, hasFillParam, hasDefaultFillColor, defaultFillColor,
239 hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
240 hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
241 hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
242 hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity,
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
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;
265 hasDefaultFillParam =
false;
266 hasDefaultFillOpacity =
false;
267 hasDefaultStrokeColor =
false;
268 hasDefaultStrokeWidth =
false;
269 hasDefaultStrokeOpacity =
false;
272 if ( !svgDoc.setContent(
getContent( path, mMissingSvg, mFetchingSvg, blocking ) ) )
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 );
285 void QgsSvgCache::replaceParamsAndCacheSvg( QgsSvgCacheEntry *entry,
bool blocking )
292 const QByteArray content =
getContent( entry->path, mMissingSvg, mFetchingSvg, blocking ) ;
293 entry->isMissingImage = content == mMissingSvg;
295 if ( !svgDoc.setContent( content ) )
301 QDomElement docElem = svgDoc.documentElement();
304 double sizeScaleFactor = calcSizeScaleFactor( entry, docElem, viewboxSize );
305 entry->viewboxSize = viewboxSize;
306 replaceElemParams( docElem, entry->fill, entry->stroke, entry->strokeWidth * sizeScaleFactor );
308 entry->svgContent = svgDoc.toByteArray( 0 );
313 entry->svgContent.replace(
"\n<tspan",
"<tspan" );
314 entry->svgContent.replace(
"</tspan>\n",
"</tspan>" );
319 double QgsSvgCache::calcSizeScaleFactor( QgsSvgCacheEntry *entry,
const QDomElement &docElem, QSizeF &viewboxSize )
const
329 if ( docElem.tagName() == QLatin1String(
"svg" ) && docElem.hasAttribute( QStringLiteral(
"viewBox" ) ) )
331 viewBox = docElem.attribute( QStringLiteral(
"viewBox" ), QString() );
333 else if ( docElem.tagName() == QLatin1String(
"svg" ) && docElem.hasAttribute( QStringLiteral(
"viewbox" ) ) )
335 viewBox = docElem.attribute( QStringLiteral(
"viewbox" ), QString() );
339 QDomElement svgElem = docElem.firstChildElement( QStringLiteral(
"svg" ) );
340 if ( !svgElem.isNull() )
342 if ( svgElem.hasAttribute( QStringLiteral(
"viewBox" ) ) )
343 viewBox = svgElem.attribute( QStringLiteral(
"viewBox" ), QString() );
344 else if ( svgElem.hasAttribute( QStringLiteral(
"viewbox" ) ) )
345 viewBox = svgElem.attribute( QStringLiteral(
"viewbox" ), QString() );
350 if ( viewBox.isEmpty() )
354 QStringList parts = viewBox.split(
' ' );
355 if ( parts.count() != 4 )
358 bool heightOk =
false;
359 double height = parts.at( 3 ).toDouble( &heightOk );
361 bool widthOk =
false;
362 double width = parts.at( 2 ).toDouble( &widthOk );
366 viewboxSize = QSizeF( width, height );
367 return width / entry->size;
376 return getContent( path, mMissingSvg, mFetchingSvg, blocking );
383 QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
384 if ( !contentType.startsWith( QLatin1String(
"image/svg+xml" ), Qt::CaseInsensitive )
385 && !contentType.startsWith( QLatin1String(
"text/plain" ), Qt::CaseInsensitive ) )
393 void QgsSvgCache::cacheImage( QgsSvgCacheEntry *entry )
400 entry->image.reset();
404 QSize imageSize = sizeForImage( *entry, viewBoxSize, scaledSize );
407 std::unique_ptr< QImage > image = qgis::make_unique< QImage >( imageSize, QImage::Format_ARGB32_Premultiplied );
410 QPainter p( image.get() );
411 QSvgRenderer r( entry->svgContent );
412 if (
qgsDoubleNear( viewBoxSize.width(), viewBoxSize.height() ) )
418 QSizeF s( viewBoxSize );
419 s.scale( scaledSize.width(), scaledSize.height(), Qt::KeepAspectRatio );
420 QRectF rect( ( imageSize.width() - s.width() ) / 2, ( imageSize.height() - s.height() ) / 2, s.width(), s.height() );
421 r.render( &p, rect );
424 mTotalSize += ( image->width() * image->height() * 32 );
425 entry->image = std::move( image );
428 void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry,
bool forceVectorOutput )
430 Q_UNUSED( forceVectorOutput )
436 entry->picture.reset();
438 bool isFixedAR = entry->fixedAspectRatio > 0;
441 std::unique_ptr< QPicture > picture = qgis::make_unique< QPicture >();
443 QSvgRenderer r( entry->svgContent );
444 double hwRatio = 1.0;
445 if ( r.viewBoxF().width() > 0 )
449 hwRatio = entry->fixedAspectRatio;
453 hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
457 double wSize = entry->size;
458 double hSize = wSize * hwRatio;
460 QSizeF s( r.viewBoxF().size() );
461 s.scale( wSize, hSize, isFixedAR ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio );
462 rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
464 QPainter p( picture.get() );
465 r.render( &p, rect );
466 entry->picture = std::move( picture );
470 QgsSvgCacheEntry *QgsSvgCache::cacheEntry(
const QString &path,
double size,
const QColor &fill,
const QColor &stroke,
double strokeWidth,
471 double widthScaleFactor,
double fixedAspectRatio,
bool blocking,
bool *isMissingImage )
473 QgsSvgCacheEntry *currentEntry =
findExistingEntry(
new QgsSvgCacheEntry( path, size, strokeWidth, widthScaleFactor, fill, stroke, fixedAspectRatio ) );
475 if ( currentEntry->svgContent.isEmpty() )
477 replaceParamsAndCacheSvg( currentEntry, blocking );
480 if ( isMissingImage )
481 *isMissingImage = currentEntry->isMissingImage;
487 void QgsSvgCache::replaceElemParams( QDomElement &elem,
const QColor &fill,
const QColor &stroke,
double strokeWidth )
495 QDomNamedNodeMap attributes = elem.attributes();
496 int nAttributes = attributes.count();
497 for (
int i = 0; i < nAttributes; ++i )
499 QDomAttr attribute = attributes.item( i ).toAttr();
501 if ( attribute.name().compare( QLatin1String(
"style" ), Qt::CaseInsensitive ) == 0 )
504 QString newAttributeString;
506 QStringList entryList = attribute.value().split(
';' );
507 QStringList::const_iterator entryIt = entryList.constBegin();
508 for ( ; entryIt != entryList.constEnd(); ++entryIt )
510 QStringList keyValueSplit = entryIt->split(
':' );
511 if ( keyValueSplit.size() < 2 )
515 const QString key = keyValueSplit.at( 0 );
516 QString value = keyValueSplit.at( 1 );
517 QString newValue = value;
518 value = value.trimmed().toLower();
520 if ( value.startsWith( QLatin1String(
"param(fill)" ) ) )
522 newValue = fill.name();
524 else if ( value.startsWith( QLatin1String(
"param(fill-opacity)" ) ) )
526 newValue = QString::number( fill.alphaF() );
528 else if ( value.startsWith( QLatin1String(
"param(outline)" ) ) )
530 newValue = stroke.name();
532 else if ( value.startsWith( QLatin1String(
"param(outline-opacity)" ) ) )
534 newValue = QString::number( stroke.alphaF() );
536 else if ( value.startsWith( QLatin1String(
"param(outline-width)" ) ) )
538 newValue = QString::number( strokeWidth );
541 if ( entryIt != entryList.constBegin() )
543 newAttributeString.append(
';' );
545 newAttributeString.append( key +
':' + newValue );
547 elem.setAttribute( attribute.name(), newAttributeString );
551 const QString value = attribute.value().trimmed().toLower();
552 if ( value.startsWith( QLatin1String(
"param(fill)" ) ) )
554 elem.setAttribute( attribute.name(), fill.name() );
556 else if ( value.startsWith( QLatin1String(
"param(fill-opacity)" ) ) )
558 elem.setAttribute( attribute.name(), fill.alphaF() );
560 else if ( value.startsWith( QLatin1String(
"param(outline)" ) ) )
562 elem.setAttribute( attribute.name(), stroke.name() );
564 else if ( value.startsWith( QLatin1String(
"param(outline-opacity)" ) ) )
566 elem.setAttribute( attribute.name(), stroke.alphaF() );
568 else if ( value.startsWith( QLatin1String(
"param(outline-width)" ) ) )
570 elem.setAttribute( attribute.name(), QString::number( strokeWidth ) );
575 QDomNodeList childList = elem.childNodes();
576 int nChildren = childList.count();
577 for (
int i = 0; i < nChildren; ++i )
579 QDomElement childElem = childList.at( i ).toElement();
580 replaceElemParams( childElem, fill, stroke, strokeWidth );
584 void QgsSvgCache::containsElemParams(
const QDomElement &elem,
bool &hasFillParam,
bool &hasDefaultFill, QColor &defaultFill,
585 bool &hasFillOpacityParam,
bool &hasDefaultFillOpacity,
double &defaultFillOpacity,
586 bool &hasStrokeParam,
bool &hasDefaultStroke, QColor &defaultStroke,
587 bool &hasStrokeWidthParam,
bool &hasDefaultStrokeWidth,
double &defaultStrokeWidth,
588 bool &hasStrokeOpacityParam,
bool &hasDefaultStrokeOpacity,
double &defaultStrokeOpacity )
const
596 if ( hasFillParam && hasStrokeParam && hasStrokeWidthParam && hasFillOpacityParam && hasStrokeOpacityParam )
602 QDomNamedNodeMap attributes = elem.attributes();
603 int nAttributes = attributes.count();
605 QStringList valueSplit;
606 for (
int i = 0; i < nAttributes; ++i )
608 QDomAttr attribute = attributes.item( i ).toAttr();
609 if ( attribute.name().compare( QLatin1String(
"style" ), Qt::CaseInsensitive ) == 0 )
612 QStringList entryList = attribute.value().split(
';' );
613 QStringList::const_iterator entryIt = entryList.constBegin();
614 for ( ; entryIt != entryList.constEnd(); ++entryIt )
616 QStringList keyValueSplit = entryIt->split(
':' );
617 if ( keyValueSplit.size() < 2 )
621 QString value = keyValueSplit.at( 1 );
622 valueSplit = value.split(
' ' );
623 if ( !hasFillParam && value.startsWith( QLatin1String(
"param(fill)" ) ) )
626 if ( valueSplit.size() > 1 )
628 defaultFill = QColor( valueSplit.at( 1 ) );
629 hasDefaultFill =
true;
632 else if ( !hasFillOpacityParam && value.startsWith( QLatin1String(
"param(fill-opacity)" ) ) )
634 hasFillOpacityParam =
true;
635 if ( valueSplit.size() > 1 )
638 double opacity = valueSplit.at( 1 ).toDouble( &ok );
641 defaultFillOpacity = opacity;
642 hasDefaultFillOpacity =
true;
646 else if ( !hasStrokeParam && value.startsWith( QLatin1String(
"param(outline)" ) ) )
648 hasStrokeParam =
true;
649 if ( valueSplit.size() > 1 )
651 defaultStroke = QColor( valueSplit.at( 1 ) );
652 hasDefaultStroke =
true;
655 else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String(
"param(outline-width)" ) ) )
657 hasStrokeWidthParam =
true;
658 if ( valueSplit.size() > 1 )
660 defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
661 hasDefaultStrokeWidth =
true;
664 else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String(
"param(outline-opacity)" ) ) )
666 hasStrokeOpacityParam =
true;
667 if ( valueSplit.size() > 1 )
670 double opacity = valueSplit.at( 1 ).toDouble( &ok );
673 defaultStrokeOpacity = opacity;
674 hasDefaultStrokeOpacity =
true;
682 QString value = attribute.value();
683 valueSplit = value.split(
' ' );
684 if ( !hasFillParam && value.startsWith( QLatin1String(
"param(fill)" ) ) )
687 if ( valueSplit.size() > 1 )
689 defaultFill = QColor( valueSplit.at( 1 ) );
690 hasDefaultFill =
true;
693 else if ( !hasFillOpacityParam && value.startsWith( QLatin1String(
"param(fill-opacity)" ) ) )
695 hasFillOpacityParam =
true;
696 if ( valueSplit.size() > 1 )
699 double opacity = valueSplit.at( 1 ).toDouble( &ok );
702 defaultFillOpacity = opacity;
703 hasDefaultFillOpacity =
true;
707 else if ( !hasStrokeParam && value.startsWith( QLatin1String(
"param(outline)" ) ) )
709 hasStrokeParam =
true;
710 if ( valueSplit.size() > 1 )
712 defaultStroke = QColor( valueSplit.at( 1 ) );
713 hasDefaultStroke =
true;
716 else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String(
"param(outline-width)" ) ) )
718 hasStrokeWidthParam =
true;
719 if ( valueSplit.size() > 1 )
721 defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
722 hasDefaultStrokeWidth =
true;
725 else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String(
"param(outline-opacity)" ) ) )
727 hasStrokeOpacityParam =
true;
728 if ( valueSplit.size() > 1 )
731 double opacity = valueSplit.at( 1 ).toDouble( &ok );
734 defaultStrokeOpacity = opacity;
735 hasDefaultStrokeOpacity =
true;
743 QDomNodeList childList = elem.childNodes();
744 int nChildren = childList.count();
745 for (
int i = 0; i < nChildren; ++i )
747 QDomElement childElem = childList.at( i ).toElement();
748 containsElemParams( childElem, hasFillParam, hasDefaultFill, defaultFill,
749 hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
750 hasStrokeParam, hasDefaultStroke, defaultStroke,
751 hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
752 hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
756 QSize QgsSvgCache::sizeForImage(
const QgsSvgCacheEntry &entry, QSizeF &viewBoxSize, QSizeF &scaledSize )
const
758 bool isFixedAR = entry.fixedAspectRatio > 0;
760 QSvgRenderer r( entry.svgContent );
761 double hwRatio = 1.0;
762 viewBoxSize = r.viewBoxF().size();
763 if ( viewBoxSize.width() > 0 )
767 hwRatio = entry.fixedAspectRatio;
771 hwRatio = viewBoxSize.height() / viewBoxSize.width();
776 scaledSize.setWidth( entry.size );
777 int wImgSize =
static_cast< int >( scaledSize.width() );
782 scaledSize.setHeight( scaledSize.width() * hwRatio );
783 int hImgSize =
static_cast< int >( scaledSize.height() );
788 return QSize( wImgSize, hImgSize );
791 QImage QgsSvgCache::imageFromCachedPicture(
const QgsSvgCacheEntry &entry )
const
795 QImage image( sizeForImage( entry, viewBoxSize, scaledSize ), QImage::Format_ARGB32_Premultiplied );
798 QPainter p( &image );
799 p.drawPicture( QPoint( 0, 0 ), *entry.picture );