00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 #include "qgssvgcache.h"
00019 #include "qgslogger.h"
00020 #include <QDomDocument>
00021 #include <QDomElement>
00022 #include <QFile>
00023 #include <QImage>
00024 #include <QPainter>
00025 #include <QPicture>
00026 #include <QSvgRenderer>
00027
00028 QgsSvgCacheEntry::QgsSvgCacheEntry(): file( QString() ), size( 0 ), outlineWidth( 0 ), widthScaleFactor( 1.0 ), rasterScaleFactor( 1.0 ), fill( Qt::black ),
00029 outline( Qt::black ), image( 0 ), picture( 0 )
00030 {
00031 }
00032
00033 QgsSvgCacheEntry::QgsSvgCacheEntry( const QString& f, double s, double ow, double wsf, double rsf, const QColor& fi, const QColor& ou ): file( f ), size( s ), outlineWidth( ow ),
00034 widthScaleFactor( wsf ), rasterScaleFactor( rsf ), fill( fi ), outline( ou ), image( 0 ), picture( 0 )
00035 {
00036 }
00037
00038
00039 QgsSvgCacheEntry::~QgsSvgCacheEntry()
00040 {
00041 delete image;
00042 delete picture;
00043 }
00044
00045 bool QgsSvgCacheEntry::operator==( const QgsSvgCacheEntry& other ) const
00046 {
00047 return ( other.file == file && other.size == size && other.outlineWidth == outlineWidth && other.widthScaleFactor == widthScaleFactor
00048 && other.rasterScaleFactor == rasterScaleFactor && other.fill == fill && other.outline == outline );
00049 }
00050
00051 int QgsSvgCacheEntry::dataSize() const
00052 {
00053 int size = svgContent.size();
00054 if ( picture )
00055 {
00056 size += picture->size();
00057 }
00058 if ( image )
00059 {
00060 size += ( image->width() * image->height() * 32 );
00061 }
00062 return size;
00063 }
00064
00065 QString file;
00066 double size;
00067 double outlineWidth;
00068 double widthScaleFactor;
00069 double rasterScaleFactor;
00070 QColor fill;
00071 QColor outline;
00072
00073 QgsSvgCache* QgsSvgCache::mInstance = 0;
00074
00075 QgsSvgCache* QgsSvgCache::instance()
00076 {
00077 if ( !mInstance )
00078 {
00079 mInstance = new QgsSvgCache();
00080 }
00081 return mInstance;
00082 }
00083
00084 QgsSvgCache::QgsSvgCache(): mTotalSize( 0 ), mLeastRecentEntry( 0 ), mMostRecentEntry( 0 )
00085 {
00086 }
00087
00088 QgsSvgCache::~QgsSvgCache()
00089 {
00090 QMultiHash< QString, QgsSvgCacheEntry* >::iterator it = mEntryLookup.begin();
00091 for ( ; it != mEntryLookup.end(); ++it )
00092 {
00093 delete it.value();
00094 }
00095 }
00096
00097
00098 const QImage& QgsSvgCache::svgAsImage( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
00099 double widthScaleFactor, double rasterScaleFactor )
00100 {
00101 QgsSvgCacheEntry* currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
00102
00103
00104
00105 if ( !currentEntry->image )
00106 {
00107 cacheImage( currentEntry );
00108 trimToMaximumSize();
00109 }
00110
00111 return *( currentEntry->image );
00112 }
00113
00114 const QPicture& QgsSvgCache::svgAsPicture( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
00115 double widthScaleFactor, double rasterScaleFactor )
00116 {
00117 QgsSvgCacheEntry* currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
00118
00119
00120
00121 if ( !currentEntry->picture )
00122 {
00123 cachePicture( currentEntry );
00124 trimToMaximumSize();
00125 }
00126
00127 return *( currentEntry->picture );
00128 }
00129
00130 QgsSvgCacheEntry* QgsSvgCache::insertSVG( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
00131 double widthScaleFactor, double rasterScaleFactor )
00132 {
00133 QgsSvgCacheEntry* entry = new QgsSvgCacheEntry( file, size, outlineWidth, widthScaleFactor, rasterScaleFactor, fill, outline );
00134
00135 replaceParamsAndCacheSvg( entry );
00136
00137 mEntryLookup.insert( file, entry );
00138
00139
00140 if ( !mMostRecentEntry )
00141 {
00142 mLeastRecentEntry = entry;
00143 mMostRecentEntry = entry;
00144 entry->previousEntry = 0;
00145 entry->nextEntry = 0;
00146 }
00147 else
00148 {
00149 entry->previousEntry = mMostRecentEntry;
00150 entry->nextEntry = 0;
00151 mMostRecentEntry->nextEntry = entry;
00152 mMostRecentEntry = entry;
00153 }
00154
00155 trimToMaximumSize();
00156 return entry;
00157 }
00158
00159 void QgsSvgCache::containsParams( const QString& path, bool& hasFillParam, QColor& defaultFillColor, bool& hasOutlineParam, QColor& defaultOutlineColor,
00160 bool& hasOutlineWidthParam, double& defaultOutlineWidth ) const
00161 {
00162 defaultFillColor = QColor( Qt::black );
00163 defaultOutlineColor = QColor( Qt::black );
00164 defaultOutlineWidth = 1.0;
00165
00166 QFile svgFile( path );
00167 if ( !svgFile.open( QIODevice::ReadOnly ) )
00168 {
00169 return;
00170 }
00171
00172 QDomDocument svgDoc;
00173 if ( !svgDoc.setContent( &svgFile ) )
00174 {
00175 return;
00176 }
00177
00178 QDomElement docElem = svgDoc.documentElement();
00179 containsElemParams( docElem, hasFillParam, defaultFillColor, hasOutlineParam, defaultOutlineColor, hasOutlineWidthParam, defaultOutlineWidth );
00180 }
00181
00182 void QgsSvgCache::replaceParamsAndCacheSvg( QgsSvgCacheEntry* entry )
00183 {
00184 if ( !entry )
00185 {
00186 return;
00187 }
00188
00189 QFile svgFile( entry->file );
00190 if ( !svgFile.open( QIODevice::ReadOnly ) )
00191 {
00192 return;
00193 }
00194
00195 QDomDocument svgDoc;
00196 if ( !svgDoc.setContent( &svgFile ) )
00197 {
00198 return;
00199 }
00200
00201
00202 QDomElement docElem = svgDoc.documentElement();
00203 replaceElemParams( docElem, entry->fill, entry->outline, entry->outlineWidth );
00204
00205 entry->svgContent = svgDoc.toByteArray();
00206 mTotalSize += entry->svgContent.size();
00207 }
00208
00209 void QgsSvgCache::cacheImage( QgsSvgCacheEntry* entry )
00210 {
00211 if ( !entry )
00212 {
00213 return;
00214 }
00215
00216 delete entry->image;
00217 entry->image = 0;
00218
00219 int imageSize = entry->size;
00220 QImage* image = new QImage( imageSize, imageSize, QImage::Format_ARGB32_Premultiplied );
00221 image->fill( 0 );
00222
00223 QPainter p( image );
00224 QSvgRenderer r( entry->svgContent );
00225 r.render( &p );
00226
00227 entry->image = image;
00228 mTotalSize += ( image->width() * image->height() * 32 );
00229 }
00230
00231 void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry )
00232 {
00233 if ( !entry )
00234 {
00235 return;
00236 }
00237
00238 delete entry->picture;
00239 entry->picture = 0;
00240
00241
00242 QPicture* picture = new QPicture();
00243 double pictureSize = entry->size / 25.4 / ( entry->rasterScaleFactor * entry->widthScaleFactor ) * picture->logicalDpiX();
00244 QRectF rect( QPointF( -pictureSize / 2.0, -pictureSize / 2.0 ), QSizeF( pictureSize, pictureSize ) );
00245
00246
00247 QSvgRenderer renderer( entry->svgContent );
00248 QPainter painter( picture );
00249 renderer.render( &painter, rect );
00250 entry->picture = picture;
00251 mTotalSize += entry->picture->size();
00252 }
00253
00254 QgsSvgCacheEntry* QgsSvgCache::cacheEntry( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
00255 double widthScaleFactor, double rasterScaleFactor )
00256 {
00257
00258 QgsSvgCacheEntry* currentEntry = 0;
00259 QList<QgsSvgCacheEntry*> entries = mEntryLookup.values( file );
00260
00261 QList<QgsSvgCacheEntry*>::iterator entryIt = entries.begin();
00262 for ( ; entryIt != entries.end(); ++entryIt )
00263 {
00264 QgsSvgCacheEntry* cacheEntry = *entryIt;
00265 if ( cacheEntry->file == file && cacheEntry->size == size && cacheEntry->fill == fill && cacheEntry->outline == outline &&
00266 cacheEntry->outlineWidth == outlineWidth && cacheEntry->widthScaleFactor == widthScaleFactor && cacheEntry->rasterScaleFactor == rasterScaleFactor )
00267 {
00268 currentEntry = cacheEntry;
00269 break;
00270 }
00271 }
00272
00273
00274
00275 if ( !currentEntry )
00276 {
00277 currentEntry = insertSVG( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
00278 }
00279 else
00280 {
00281 takeEntryFromList( currentEntry );
00282 if ( !mMostRecentEntry )
00283 {
00284 mMostRecentEntry = currentEntry;
00285 mLeastRecentEntry = currentEntry;
00286 }
00287 else
00288 {
00289 mMostRecentEntry->nextEntry = currentEntry;
00290 currentEntry->previousEntry = mMostRecentEntry;
00291 currentEntry->nextEntry = 0;
00292 mMostRecentEntry = currentEntry;
00293 }
00294 }
00295
00296
00297
00298
00299 return currentEntry;
00300 }
00301
00302 void QgsSvgCache::replaceElemParams( QDomElement& elem, const QColor& fill, const QColor& outline, double outlineWidth )
00303 {
00304 if ( elem.isNull() )
00305 {
00306 return;
00307 }
00308
00309
00310 QDomNamedNodeMap attributes = elem.attributes();
00311 int nAttributes = attributes.count();
00312 for ( int i = 0; i < nAttributes; ++i )
00313 {
00314 QDomAttr attribute = attributes.item( i ).toAttr();
00315
00316 if ( attribute.name().compare( "style", Qt::CaseInsensitive ) == 0 )
00317 {
00318
00319 QString newAttributeString;
00320
00321 QStringList entryList = attribute.value().split( ';' );
00322 QStringList::const_iterator entryIt = entryList.constBegin();
00323 for ( ; entryIt != entryList.constEnd(); ++entryIt )
00324 {
00325 QStringList keyValueSplit = entryIt->split( ':' );
00326 if ( keyValueSplit.size() < 2 )
00327 {
00328 continue;
00329 }
00330 QString key = keyValueSplit.at( 0 );
00331 QString value = keyValueSplit.at( 1 );
00332 if ( value.startsWith( "param(fill" ) )
00333 {
00334 value = fill.name();
00335 }
00336 else if ( value.startsWith( "param(outline)" ) )
00337 {
00338 value = outline.name();
00339 }
00340 else if ( value.startsWith( "param(outline-width)" ) )
00341 {
00342 value = QString::number( outlineWidth );
00343 }
00344
00345 if ( entryIt != entryList.constBegin() )
00346 {
00347 newAttributeString.append( ";" );
00348 }
00349 newAttributeString.append( key + ":" + value );
00350 }
00351 elem.setAttribute( attribute.name(), newAttributeString );
00352 }
00353 else
00354 {
00355 QString value = attribute.value();
00356 if ( value.startsWith( "param(fill)" ) )
00357 {
00358 elem.setAttribute( attribute.name(), fill.name() );
00359 }
00360 else if ( value.startsWith( "param(outline)" ) )
00361 {
00362 elem.setAttribute( attribute.name(), outline.name() );
00363 }
00364 else if ( value.startsWith( "param(outline-width)" ) )
00365 {
00366 elem.setAttribute( attribute.name(), QString::number( outlineWidth ) );
00367 }
00368 }
00369 }
00370
00371 QDomNodeList childList = elem.childNodes();
00372 int nChildren = childList.count();
00373 for ( int i = 0; i < nChildren; ++i )
00374 {
00375 QDomElement childElem = childList.at( i ).toElement();
00376 replaceElemParams( childElem, fill, outline, outlineWidth );
00377 }
00378 }
00379
00380 void QgsSvgCache::containsElemParams( const QDomElement& elem, bool& hasFillParam, QColor& defaultFill, bool& hasOutlineParam, QColor& defaultOutline,
00381 bool& hasOutlineWidthParam, double& defaultOutlineWidth ) const
00382 {
00383 if ( elem.isNull() )
00384 {
00385 return;
00386 }
00387
00388
00389 if ( hasFillParam && hasOutlineParam && hasOutlineWidthParam )
00390 {
00391 return;
00392 }
00393
00394
00395 QDomNamedNodeMap attributes = elem.attributes();
00396 int nAttributes = attributes.count();
00397
00398 QStringList valueSplit;
00399 for ( int i = 0; i < nAttributes; ++i )
00400 {
00401 QDomAttr attribute = attributes.item( i ).toAttr();
00402 if ( attribute.name().compare( "style", Qt::CaseInsensitive ) == 0 )
00403 {
00404
00405 QStringList entryList = attribute.value().split( ';' );
00406 QStringList::const_iterator entryIt = entryList.constBegin();
00407 for ( ; entryIt != entryList.constEnd(); ++entryIt )
00408 {
00409 QStringList keyValueSplit = entryIt->split( ':' );
00410 if ( keyValueSplit.size() < 2 )
00411 {
00412 continue;
00413 }
00414 QString key = keyValueSplit.at( 0 );
00415 QString value = keyValueSplit.at( 1 );
00416 valueSplit = value.split( " " );
00417 if ( !hasFillParam && value.startsWith( "param(fill)" ) )
00418 {
00419 hasFillParam = true;
00420 if ( valueSplit.size() > 1 )
00421 {
00422 defaultFill = QColor( valueSplit.at( 1 ) );
00423 }
00424 }
00425 else if ( !hasOutlineParam && value.startsWith( "param(outline)" ) )
00426 {
00427 hasOutlineParam = true;
00428 if ( valueSplit.size() > 1 )
00429 {
00430 defaultOutline = QColor( valueSplit.at( 1 ) );
00431 }
00432 }
00433 else if ( !hasOutlineWidthParam && value.startsWith( "param(outline-width)" ) )
00434 {
00435 hasOutlineWidthParam = true;
00436 if ( valueSplit.size() > 1 )
00437 {
00438 defaultOutlineWidth = valueSplit.at( 1 ).toDouble();
00439 }
00440 }
00441 }
00442 }
00443 else
00444 {
00445 QString value = attribute.value();
00446 valueSplit = value.split( " " );
00447 if ( !hasFillParam && value.startsWith( "param(fill)" ) )
00448 {
00449 hasFillParam = true;
00450 if ( valueSplit.size() > 1 )
00451 {
00452 defaultFill = QColor( valueSplit.at( 1 ) );
00453 }
00454 }
00455 else if ( !hasOutlineParam && value.startsWith( "param(outline)" ) )
00456 {
00457 hasOutlineParam = true;
00458 if ( valueSplit.size() > 1 )
00459 {
00460 defaultOutline = QColor( valueSplit.at( 1 ) );
00461 }
00462 }
00463 else if ( !hasOutlineWidthParam && value.startsWith( "param(outline-width)" ) )
00464 {
00465 hasOutlineWidthParam = true;
00466 if ( valueSplit.size() > 1 )
00467 {
00468 defaultOutlineWidth = valueSplit.at( 1 ).toDouble();
00469 }
00470 }
00471 }
00472 }
00473
00474
00475 QDomNodeList childList = elem.childNodes();
00476 int nChildren = childList.count();
00477 for ( int i = 0; i < nChildren; ++i )
00478 {
00479 QDomElement childElem = childList.at( i ).toElement();
00480 containsElemParams( childElem, hasFillParam, defaultFill, hasOutlineParam, defaultOutline, hasOutlineWidthParam, defaultOutlineWidth );
00481 }
00482 }
00483
00484 void QgsSvgCache::removeCacheEntry( QString s, QgsSvgCacheEntry* entry )
00485 {
00486 delete entry;
00487 mEntryLookup.remove( s , entry );
00488 }
00489
00490 void QgsSvgCache::printEntryList()
00491 {
00492 QgsDebugMsg( "****************svg cache entry list*************************" );
00493 QgsDebugMsg( "Cache size: " + QString::number( mTotalSize ) );
00494 QgsSvgCacheEntry* entry = mLeastRecentEntry;
00495 while ( entry )
00496 {
00497 QgsDebugMsg( "***Entry:" );
00498 QgsDebugMsg( "File:" + entry->file );
00499 QgsDebugMsg( "Size:" + QString::number( entry->size ) );
00500 QgsDebugMsg( "Width scale factor" + QString::number( entry->widthScaleFactor ) );
00501 QgsDebugMsg( "Raster scale factor" + QString::number( entry->rasterScaleFactor ) );
00502 entry = entry->nextEntry;
00503 }
00504 }
00505
00506 void QgsSvgCache::trimToMaximumSize()
00507 {
00508 QgsSvgCacheEntry* entry = mLeastRecentEntry;
00509 while ( entry && ( mTotalSize > mMaximumSize ) )
00510 {
00511 QgsSvgCacheEntry* bkEntry = entry;
00512 entry = entry->nextEntry;
00513
00514 takeEntryFromList( bkEntry );
00515 mEntryLookup.remove( bkEntry->file, bkEntry );
00516 mTotalSize -= bkEntry->dataSize();
00517 delete bkEntry;
00518 }
00519 }
00520
00521 void QgsSvgCache::takeEntryFromList( QgsSvgCacheEntry* entry )
00522 {
00523 if ( !entry )
00524 {
00525 return;
00526 }
00527
00528 if ( entry->previousEntry )
00529 {
00530 entry->previousEntry->nextEntry = entry->nextEntry;
00531 }
00532 else
00533 {
00534 mLeastRecentEntry = entry->nextEntry;
00535 }
00536 if ( entry->nextEntry )
00537 {
00538 entry->nextEntry->previousEntry = entry->previousEntry;
00539 }
00540 else
00541 {
00542 mMostRecentEntry = entry->previousEntry;
00543 }
00544 }
00545