25 #include <QApplication> 26 #include <QCoreApplication> 28 #include <QDomDocument> 29 #include <QDomElement> 34 #include <QSvgRenderer> 36 #include <QNetworkReply> 37 #include <QNetworkRequest> 43 , widthScaleFactor( 1.0 )
44 , rasterScaleFactor( 1.0 )
46 , outline( Qt::black )
92 size += (
image->width() *
image->height() * 32 );
106 , mLeastRecentEntry( 0 )
107 , mMostRecentEntry( 0 )
109 mMissingSvg = QString(
"<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toAscii();
114 QMultiHash< QString, QgsSvgCacheEntry* >::iterator it = mEntryLookup.begin();
115 for ( ; it != mEntryLookup.end(); ++it )
122 const QImage&
QgsSvgCache::svgAsImage(
const QString& file,
double size,
const QColor& fill,
const QColor& outline,
double outlineWidth,
123 double widthScaleFactor,
double rasterScaleFactor,
bool& fitsInCache )
125 QMutexLocker locker( &mMutex );
133 if ( !currentEntry->
image )
136 double hwRatio = 1.0;
137 if ( r.viewBoxF().width() > 0 )
139 hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
141 long cachedDataSize = 0;
142 cachedDataSize += currentEntry->
svgContent.size();
143 cachedDataSize += ( int )( currentEntry->
size * currentEntry->
size * hwRatio * 32 );
144 if ( cachedDataSize > mMaximumSize / 2 )
147 delete currentEntry->
image;
148 currentEntry->
image = 0;
164 return *( currentEntry->
image );
167 const QPicture&
QgsSvgCache::svgAsPicture(
const QString& file,
double size,
const QColor& fill,
const QColor& outline,
double outlineWidth,
168 double widthScaleFactor,
double rasterScaleFactor,
bool forceVectorOutput )
170 QMutexLocker locker( &mMutex );
182 return *( currentEntry->
picture );
185 const QByteArray&
QgsSvgCache::svgContent(
const QString& file,
double size,
const QColor& fill,
const QColor& outline,
double outlineWidth,
186 double widthScaleFactor,
double rasterScaleFactor )
188 QMutexLocker locker( &mMutex );
196 double widthScaleFactor,
double rasterScaleFactor )
205 mEntryLookup.insert( file, entry );
208 if ( !mMostRecentEntry )
210 mLeastRecentEntry = entry;
211 mMostRecentEntry = entry;
220 mMostRecentEntry = entry;
227 void QgsSvgCache::containsParams(
const QString& path,
bool& hasFillParam, QColor& defaultFillColor,
bool& hasOutlineParam, QColor& defaultOutlineColor,
228 bool& hasOutlineWidthParam,
double& defaultOutlineWidth )
const 230 hasFillParam =
false;
231 hasOutlineParam =
false;
232 hasOutlineWidthParam =
false;
233 defaultFillColor = QColor( Qt::black );
234 defaultOutlineColor = QColor( Qt::black );
235 defaultOutlineWidth = 1.0;
243 QDomElement docElem = svgDoc.documentElement();
244 containsElemParams( docElem, hasFillParam, defaultFillColor, hasOutlineParam, defaultOutlineColor, hasOutlineWidthParam, defaultOutlineWidth );
261 QDomElement docElem = svgDoc.documentElement();
271 QFile svgFile( path );
272 if ( svgFile.exists() )
274 if ( svgFile.open( QIODevice::ReadOnly ) )
276 return svgFile.readAll();
285 if ( !path.contains(
"://" ) )
291 if ( !svgUrl.isValid() )
297 if ( svgUrl.scheme().compare(
"file", Qt::CaseInsensitive ) == 0 )
299 svgFile.setFileName( svgUrl.toLocalFile() );
300 if ( svgFile.exists() )
302 if ( svgFile.open( QIODevice::ReadOnly ) )
304 return svgFile.readAll();
313 QNetworkReply *reply = 0;
320 QgsDebugMsg( QString(
"get svg: %1" ).arg( svgUrl.toString() ) );
321 QNetworkRequest request( svgUrl );
322 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
323 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
326 connect( reply, SIGNAL( downloadProgress( qint64, qint64 ) ),
this, SLOT( downloadProgress( qint64, qint64 ) ) );
332 while ( !reply->isFinished() )
334 QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents, 500 );
337 if ( reply->error() != QNetworkReply::NoError )
339 QgsMessageLog::logMessage(
tr(
"SVG request failed [error: %1 - url: %2]" ).arg( reply->errorString() ).arg( reply->url().toString() ),
tr(
"SVG" ) );
341 reply->deleteLater();
345 QVariant redirect = reply->attribute( QNetworkRequest::RedirectionTargetAttribute );
346 if ( redirect.isNull() )
354 svgUrl = redirect.toUrl();
355 reply->deleteLater();
358 QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
359 if ( !status.isNull() && status.toInt() >= 400 )
361 QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
362 QgsMessageLog::logMessage(
tr(
"SVG request error [status: %1 - reason phrase: %2]" ).arg( status.toInt() ).arg( phrase.toString() ),
tr(
"SVG" ) );
364 reply->deleteLater();
368 QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
370 if ( !contentType.startsWith(
"image/svg+xml", Qt::CaseInsensitive ) )
372 reply->deleteLater();
377 QByteArray ba = reply->readAll();
378 reply->deleteLater();
394 double hwRatio = 1.0;
395 if ( r.viewBoxF().width() > 0 )
397 hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
399 double wSize = entry->
size;
400 int wImgSize = ( int )wSize;
405 double hSize = wSize * hwRatio;
406 int hImgSize = ( int )hSize;
412 QImage* image =
new QImage( wImgSize, hImgSize, QImage::Format_ARGB32_Premultiplied );
416 if ( r.viewBoxF().width() == r.viewBoxF().height() )
422 QSizeF s( r.viewBoxF().size() );
423 s.scale( wSize, hSize, Qt::KeepAspectRatio );
424 QRectF rect(( wImgSize - s.width() ) / 2, ( hImgSize - s.height() ) / 2, s.width(), s.height() );
425 r.render( &p, rect );
428 entry->
image = image;
429 mTotalSize += ( image->width() * image->height() * 32 );
434 Q_UNUSED( forceVectorOutput );
444 QPicture* picture =
new QPicture();
447 double hwRatio = 1.0;
448 if ( r.viewBoxF().width() > 0 )
450 hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
453 double wSize = entry->
size;
454 double hSize = wSize * hwRatio;
455 QSizeF s( r.viewBoxF().size() );
456 s.scale( wSize, hSize, Qt::KeepAspectRatio );
457 rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
459 QPainter p( picture );
460 r.render( &p, rect );
462 mTotalSize += entry->
picture->size();
466 double widthScaleFactor,
double rasterScaleFactor )
470 QList<QgsSvgCacheEntry*> entries = mEntryLookup.values( file );
472 QList<QgsSvgCacheEntry*>::iterator entryIt = entries.begin();
473 for ( ; entryIt != entries.end(); ++entryIt )
488 currentEntry =
insertSVG( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
493 if ( !mMostRecentEntry )
495 mMostRecentEntry = currentEntry;
496 mLeastRecentEntry = currentEntry;
500 mMostRecentEntry->
nextEntry = currentEntry;
503 mMostRecentEntry = currentEntry;
513 void QgsSvgCache::replaceElemParams( QDomElement& elem,
const QColor& fill,
const QColor& outline,
double outlineWidth )
521 QDomNamedNodeMap attributes = elem.attributes();
522 int nAttributes = attributes.count();
523 for (
int i = 0; i < nAttributes; ++i )
525 QDomAttr attribute = attributes.item( i ).toAttr();
527 if ( attribute.name().compare(
"style", Qt::CaseInsensitive ) == 0 )
530 QString newAttributeString;
532 QStringList entryList = attribute.value().split(
';' );
533 QStringList::const_iterator entryIt = entryList.constBegin();
534 for ( ; entryIt != entryList.constEnd(); ++entryIt )
536 QStringList keyValueSplit = entryIt->split(
':' );
537 if ( keyValueSplit.size() < 2 )
541 QString key = keyValueSplit.at( 0 );
542 QString value = keyValueSplit.at( 1 );
543 if ( value.startsWith(
"param(fill" ) )
547 else if ( value.startsWith(
"param(outline)" ) )
549 value = outline.name();
551 else if ( value.startsWith(
"param(outline-width)" ) )
553 value = QString::number( outlineWidth );
556 if ( entryIt != entryList.constBegin() )
558 newAttributeString.append(
";" );
560 newAttributeString.append( key +
":" + value );
562 elem.setAttribute( attribute.name(), newAttributeString );
566 QString value = attribute.value();
567 if ( value.startsWith(
"param(fill)" ) )
569 elem.setAttribute( attribute.name(), fill.name() );
571 else if ( value.startsWith(
"param(outline)" ) )
573 elem.setAttribute( attribute.name(), outline.name() );
575 else if ( value.startsWith(
"param(outline-width)" ) )
577 elem.setAttribute( attribute.name(), QString::number( outlineWidth ) );
582 QDomNodeList childList = elem.childNodes();
583 int nChildren = childList.count();
584 for (
int i = 0; i < nChildren; ++i )
586 QDomElement childElem = childList.at( i ).toElement();
587 replaceElemParams( childElem, fill, outline, outlineWidth );
591 void QgsSvgCache::containsElemParams(
const QDomElement& elem,
bool& hasFillParam, QColor& defaultFill,
bool& hasOutlineParam, QColor& defaultOutline,
592 bool& hasOutlineWidthParam,
double& defaultOutlineWidth )
const 600 if ( hasFillParam && hasOutlineParam && hasOutlineWidthParam )
606 QDomNamedNodeMap attributes = elem.attributes();
607 int nAttributes = attributes.count();
609 QStringList valueSplit;
610 for (
int i = 0; i < nAttributes; ++i )
612 QDomAttr attribute = attributes.item( i ).toAttr();
613 if ( attribute.name().compare(
"style", Qt::CaseInsensitive ) == 0 )
616 QStringList entryList = attribute.value().split(
';' );
617 QStringList::const_iterator entryIt = entryList.constBegin();
618 for ( ; entryIt != entryList.constEnd(); ++entryIt )
620 QStringList keyValueSplit = entryIt->split(
':' );
621 if ( keyValueSplit.size() < 2 )
625 QString key = keyValueSplit.at( 0 );
626 QString value = keyValueSplit.at( 1 );
627 valueSplit = value.split(
" " );
628 if ( !hasFillParam && value.startsWith(
"param(fill)" ) )
631 if ( valueSplit.size() > 1 )
633 defaultFill = QColor( valueSplit.at( 1 ) );
636 else if ( !hasOutlineParam && value.startsWith(
"param(outline)" ) )
638 hasOutlineParam =
true;
639 if ( valueSplit.size() > 1 )
641 defaultOutline = QColor( valueSplit.at( 1 ) );
644 else if ( !hasOutlineWidthParam && value.startsWith(
"param(outline-width)" ) )
646 hasOutlineWidthParam =
true;
647 if ( valueSplit.size() > 1 )
649 defaultOutlineWidth = valueSplit.at( 1 ).toDouble();
656 QString value = attribute.value();
657 valueSplit = value.split(
" " );
658 if ( !hasFillParam && value.startsWith(
"param(fill)" ) )
661 if ( valueSplit.size() > 1 )
663 defaultFill = QColor( valueSplit.at( 1 ) );
666 else if ( !hasOutlineParam && value.startsWith(
"param(outline)" ) )
668 hasOutlineParam =
true;
669 if ( valueSplit.size() > 1 )
671 defaultOutline = QColor( valueSplit.at( 1 ) );
674 else if ( !hasOutlineWidthParam && value.startsWith(
"param(outline-width)" ) )
676 hasOutlineWidthParam =
true;
677 if ( valueSplit.size() > 1 )
679 defaultOutlineWidth = valueSplit.at( 1 ).toDouble();
686 QDomNodeList childList = elem.childNodes();
687 int nChildren = childList.count();
688 for (
int i = 0; i < nChildren; ++i )
690 QDomElement childElem = childList.at( i ).toElement();
691 containsElemParams( childElem, hasFillParam, defaultFill, hasOutlineParam, defaultOutline, hasOutlineWidthParam, defaultOutlineWidth );
698 mEntryLookup.remove( s, entry );
701 void QgsSvgCache::printEntryList()
703 QgsDebugMsg(
"****************svg cache entry list*************************" );
704 QgsDebugMsg(
"Cache size: " + QString::number( mTotalSize ) );
720 if ( mLeastRecentEntry == mMostRecentEntry )
725 while ( entry && ( mTotalSize > mMaximumSize ) )
731 mEntryLookup.remove( bkEntry->
lookupKey, bkEntry );
762 void QgsSvgCache::downloadProgress( qint64 bytesReceived, qint64 bytesTotal )
764 QString msg =
tr(
"%1 of %2 bytes of svg image downloaded." ).arg( bytesReceived ).arg( bytesTotal < 0 ? QString(
"unknown number of" ) : QString::number( bytesTotal ) );
QgsSvgCacheEntry * previousEntry
int dataSize() const
Return memory usage in bytes.
const QPicture & svgAsPicture(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor, bool forceVectorOutput=false)
Get SVG as QPicture&.
QgsSvgCacheEntry * cacheEntry(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor)
Returns entry from cache or creates a new entry if it does not exist already.
A cache for images / pictures derived from svg files.
static QgsNetworkAccessManager * instance()
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
static void logMessage(QString message, QString tag=QString::null, MessageLevel level=WARNING)
add a message to the instance (and create it if necessary)
void containsParams(const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasOutlineParam, QColor &defaultOutlineColor, bool &hasOutlineWidthParam, double &defaultOutlineWidth) const
Tests if an svg file contains parameters for fill, outline color, outline width.
static QgsSvgCache * instance()
const QImage & svgAsImage(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor, bool &fitsInCache)
Get SVG as QImage.
QByteArray getImageData(const QString &path) const
Get image data.
static QString symbolNameToPath(QString name)
Get symbol's path from its name.
void statusChanged(const QString &theStatusQString)
Emit a signal to be caught by qgisapp and display a msg on status bar.
const QByteArray & svgContent(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor)
Get SVG content.
void cachePicture(QgsSvgCacheEntry *entry, bool forceVectorOutput=false)
void trimToMaximumSize()
Removes the least used items until the maximum size is under the limit.
void takeEntryFromList(QgsSvgCacheEntry *entry)
QString lookupKey
Lookup key used by QgsSvgCache's hashtable (relative or absolute path). Needed for removal from the h...
void cacheImage(QgsSvgCacheEntry *entry)
void replaceParamsAndCacheSvg(QgsSvgCacheEntry *entry)
QgsSvgCache(QObject *parent=0)
protected constructor
QgsSvgCacheEntry * insertSVG(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor, double rasterScaleFactor)
Creates new cache entry and returns pointer to it.
QString file
Absolute path to SVG file.
bool operator==(const QgsSvgCacheEntry &other) const
Don't consider image, picture, last used timestamp for comparison.
QgsSvgCacheEntry * nextEntry