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"
21 #include "qgsnetworkaccessmanager.h"
22 #include "qgsmessagelog.h"
23 #include "qgssymbollayerutils.h"
24 #include "qgsnetworkcontentfetchertask.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
40 ///@cond PRIVATE
41
42 //
43 // QgsSvgCacheEntry
44 //
45
QgsSvgCacheEntry(const QString & path,double size,double strokeWidth,double widthScaleFactor,const QColor & fill,const QColor & stroke,double fixedAspectRatio)46 QgsSvgCacheEntry::QgsSvgCacheEntry( const QString &path, double size, double strokeWidth, double widthScaleFactor, const QColor &fill, const QColor &stroke, double fixedAspectRatio )
47 : QgsAbstractContentCacheEntry( path )
48 , size( size )
49 , strokeWidth( strokeWidth )
50 , widthScaleFactor( widthScaleFactor )
51 , fixedAspectRatio( fixedAspectRatio )
52 , fill( fill )
53 , stroke( stroke )
54 {
55 }
56
isEqual(const QgsAbstractContentCacheEntry * other) const57 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
dataSize() const74 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
dump() const88 void QgsSvgCacheEntry::dump() const
89 {
90 QgsDebugMsgLevel( QStringLiteral( "path: %1, size %2, width scale factor %3" ).arg( path ).arg( size ).arg( widthScaleFactor ), 4 );
91 }
92 ///@endcond
93
94
95 //
96 // QgsSvgCache
97 //
98
QgsSvgCache(QObject * parent)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
119 connect( this, &QgsAbstractContentCacheBase::remoteContentFetched, this, &QgsSvgCache::remoteSvgFetched );
120 }
121
svgAsImage(const QString & file,double size,const QColor & fill,const QColor & stroke,double strokeWidth,double widthScaleFactor,bool & fitsInCache,double fixedAspectRatio,bool blocking)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 }
172 trimToMaximumSize();
173 }
174 else
175 {
176 result = *( currentEntry->image );
177 }
178
179 return result;
180 }
181
svgAsPicture(const QString & path,double size,const QColor & fill,const QColor & stroke,double strokeWidth,double widthScaleFactor,bool forceVectorOutput,double fixedAspectRatio,bool blocking)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 );
194 trimToMaximumSize();
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
svgContent(const QString & path,double size,const QColor & fill,const QColor & stroke,double strokeWidth,double widthScaleFactor,double fixedAspectRatio,bool blocking,bool * isMissingImage)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 )
208 {
209 QMutexLocker locker( &mMutex );
210
211 QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, blocking, isMissingImage );
212
213 return currentEntry->svgContent;
214 }
215
svgViewboxSize(const QString & path,double size,const QColor & fill,const QColor & stroke,double strokeWidth,double widthScaleFactor,double fixedAspectRatio,bool blocking)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
containsParams(const QString & path,bool & hasFillParam,QColor & defaultFillColor,bool & hasStrokeParam,QColor & defaultStrokeColor,bool & hasStrokeWidthParam,double & defaultStrokeWidth,bool blocking) const225 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
containsParams(const QString & path,bool & hasFillParam,bool & hasDefaultFillParam,QColor & defaultFillColor,bool & hasFillOpacityParam,bool & hasDefaultFillOpacity,double & defaultFillOpacity,bool & hasStrokeParam,bool & hasDefaultStrokeColor,QColor & defaultStrokeColor,bool & hasStrokeWidthParam,bool & hasDefaultStrokeWidth,double & defaultStrokeWidth,bool & hasStrokeOpacityParam,bool & hasDefaultStrokeOpacity,double & defaultStrokeOpacity,bool blocking) const246 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
replaceParamsAndCacheSvg(QgsSvgCacheEntry * entry,bool blocking)285 void QgsSvgCache::replaceParamsAndCacheSvg( QgsSvgCacheEntry *entry, bool blocking )
286 {
287 if ( !entry )
288 {
289 return;
290 }
291
292 const QByteArray content = getContent( entry->path, mMissingSvg, mFetchingSvg, blocking ) ;
293 entry->isMissingImage = content == mMissingSvg;
294 QDomDocument svgDoc;
295 if ( !svgDoc.setContent( content ) )
296 {
297 return;
298 }
299
300 //replace fill color, stroke color, stroke with in all nodes
301 QDomElement docElem = svgDoc.documentElement();
302
303 QSizeF viewboxSize;
304 double sizeScaleFactor = calcSizeScaleFactor( entry, docElem, viewboxSize );
305 entry->viewboxSize = viewboxSize;
306 replaceElemParams( docElem, entry->fill, entry->stroke, entry->strokeWidth * sizeScaleFactor );
307
308 entry->svgContent = svgDoc.toByteArray( 0 );
309
310
311 // toByteArray screws up tspans inside text by adding new lines before and after each span... this should help, at the
312 // risk of potentially breaking some svgs where the newline is desired
313 entry->svgContent.replace( "\n<tspan", "<tspan" );
314 entry->svgContent.replace( "</tspan>\n", "</tspan>" );
315
316 mTotalSize += entry->svgContent.size();
317 }
318
calcSizeScaleFactor(QgsSvgCacheEntry * entry,const QDomElement & docElem,QSizeF & viewboxSize) const319 double QgsSvgCache::calcSizeScaleFactor( QgsSvgCacheEntry *entry, const QDomElement &docElem, QSizeF &viewboxSize ) const
320 {
321 QString viewBox;
322
323 //bad size
324 if ( !entry || qgsDoubleNear( entry->size, 0.0 ) )
325 return 1.0;
326
327 //find svg viewbox attribute
328 //first check if docElem is svg element
329 if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "viewBox" ) ) )
330 {
331 viewBox = docElem.attribute( QStringLiteral( "viewBox" ), QString() );
332 }
333 else if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "viewbox" ) ) )
334 {
335 viewBox = docElem.attribute( QStringLiteral( "viewbox" ), QString() );
336 }
337 else
338 {
339 QDomElement svgElem = docElem.firstChildElement( QStringLiteral( "svg" ) );
340 if ( !svgElem.isNull() )
341 {
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() );
346 }
347 }
348
349 //could not find valid viewbox attribute
350 if ( viewBox.isEmpty() )
351 {
352 // trying looking for width/height and use them as a fallback
353 if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "width" ) ) )
354 {
355 const QString widthString = docElem.attribute( QStringLiteral( "width" ) );
356 const QRegularExpression measureRegEx( QStringLiteral( "([\\d\\.]+).*?$" ) );
357 const QRegularExpressionMatch widthMatch = measureRegEx.match( widthString );
358 if ( widthMatch.hasMatch() )
359 {
360 double width = widthMatch.captured( 1 ).toDouble();
361 const QString heightString = docElem.attribute( QStringLiteral( "height" ) );
362
363 const QRegularExpressionMatch heightMatch = measureRegEx.match( heightString );
364 if ( heightMatch.hasMatch() )
365 {
366 double height = heightMatch.captured( 1 ).toDouble();
367 viewboxSize = QSizeF( width, height );
368 return width / entry->size;
369 }
370 }
371 }
372
373 return 1.0;
374 }
375
376
377 //width should be 3rd element in a 4 part space delimited string
378 QStringList parts = viewBox.split( ' ' );
379 if ( parts.count() != 4 )
380 return 1.0;
381
382 bool heightOk = false;
383 double height = parts.at( 3 ).toDouble( &heightOk );
384
385 bool widthOk = false;
386 double width = parts.at( 2 ).toDouble( &widthOk );
387 if ( widthOk )
388 {
389 if ( heightOk )
390 viewboxSize = QSizeF( width, height );
391 return width / entry->size;
392 }
393
394 return 1.0;
395 }
396
397
getImageData(const QString & path,bool blocking) const398 QByteArray QgsSvgCache::getImageData( const QString &path, bool blocking ) const
399 {
400 return getContent( path, mMissingSvg, mFetchingSvg, blocking );
401 };
402
checkReply(QNetworkReply * reply,const QString & path) const403 bool QgsSvgCache::checkReply( QNetworkReply *reply, const QString &path ) const
404 {
405 // we accept both real SVG mime types AND plain text types - because some sites
406 // (notably github) serve up svgs as raw text
407 QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
408 if ( !contentType.startsWith( QLatin1String( "image/svg+xml" ), Qt::CaseInsensitive )
409 && !contentType.startsWith( QLatin1String( "text/plain" ), Qt::CaseInsensitive ) )
410 {
411 QgsMessageLog::logMessage( tr( "Unexpected MIME type %1 received for %2" ).arg( contentType, path ), tr( "SVG" ) );
412 return false;
413 }
414 return true;
415 }
416
cacheImage(QgsSvgCacheEntry * entry)417 void QgsSvgCache::cacheImage( QgsSvgCacheEntry *entry )
418 {
419 if ( !entry )
420 {
421 return;
422 }
423
424 entry->image.reset();
425
426 QSizeF viewBoxSize;
427 QSizeF scaledSize;
428 QSize imageSize = sizeForImage( *entry, viewBoxSize, scaledSize );
429
430 // cast double image sizes to int for QImage
431 std::unique_ptr< QImage > image = qgis::make_unique< QImage >( imageSize, QImage::Format_ARGB32_Premultiplied );
432 image->fill( 0 ); // transparent background
433
434 const bool isFixedAR = entry->fixedAspectRatio > 0;
435
436 QPainter p( image.get() );
437 QSvgRenderer r( entry->svgContent );
438 if ( qgsDoubleNear( viewBoxSize.width(), viewBoxSize.height() ) )
439 {
440 r.render( &p );
441 }
442 else
443 {
444 QSizeF s( viewBoxSize );
445 s.scale( scaledSize.width(), scaledSize.height(), isFixedAR ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio );
446 QRectF rect( ( imageSize.width() - s.width() ) / 2, ( imageSize.height() - s.height() ) / 2, s.width(), s.height() );
447 r.render( &p, rect );
448 }
449
450 mTotalSize += ( image->width() * image->height() * 32 );
451 entry->image = std::move( image );
452 }
453
cachePicture(QgsSvgCacheEntry * entry,bool forceVectorOutput)454 void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry, bool forceVectorOutput )
455 {
456 Q_UNUSED( forceVectorOutput )
457 if ( !entry )
458 {
459 return;
460 }
461
462 entry->picture.reset();
463
464 bool isFixedAR = entry->fixedAspectRatio > 0;
465
466 //correct QPictures dpi correction
467 std::unique_ptr< QPicture > picture = qgis::make_unique< QPicture >();
468 QRectF rect;
469 QSvgRenderer r( entry->svgContent );
470 double hwRatio = 1.0;
471 if ( r.viewBoxF().width() > 0 )
472 {
473 if ( isFixedAR )
474 {
475 hwRatio = entry->fixedAspectRatio;
476 }
477 else
478 {
479 hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
480 }
481 }
482
483 double wSize = entry->size;
484 double hSize = wSize * hwRatio;
485
486 QSizeF s( r.viewBoxF().size() );
487 s.scale( wSize, hSize, isFixedAR ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio );
488 rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
489
490 QPainter p( picture.get() );
491 r.render( &p, rect );
492 entry->picture = std::move( picture );
493 mTotalSize += entry->picture->size();
494 }
495
cacheEntry(const QString & path,double size,const QColor & fill,const QColor & stroke,double strokeWidth,double widthScaleFactor,double fixedAspectRatio,bool blocking,bool * isMissingImage)496 QgsSvgCacheEntry *QgsSvgCache::cacheEntry( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
497 double widthScaleFactor, double fixedAspectRatio, bool blocking, bool *isMissingImage )
498 {
499 QgsSvgCacheEntry *currentEntry = findExistingEntry( new QgsSvgCacheEntry( path, size, strokeWidth, widthScaleFactor, fill, stroke, fixedAspectRatio ) );
500
501 if ( currentEntry->svgContent.isEmpty() )
502 {
503 replaceParamsAndCacheSvg( currentEntry, blocking );
504 }
505
506 if ( isMissingImage )
507 *isMissingImage = currentEntry->isMissingImage;
508
509 return currentEntry;
510 }
511
512
replaceElemParams(QDomElement & elem,const QColor & fill,const QColor & stroke,double strokeWidth)513 void QgsSvgCache::replaceElemParams( QDomElement &elem, const QColor &fill, const QColor &stroke, double strokeWidth )
514 {
515 if ( elem.isNull() )
516 {
517 return;
518 }
519
520 //go through attributes
521 QDomNamedNodeMap attributes = elem.attributes();
522 int nAttributes = attributes.count();
523 for ( int i = 0; i < nAttributes; ++i )
524 {
525 QDomAttr attribute = attributes.item( i ).toAttr();
526 //e.g. style="fill:param(fill);param(stroke)"
527 if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
528 {
529 //entries separated by ';'
530 QString newAttributeString;
531
532 QStringList entryList = attribute.value().split( ';' );
533 QStringList::const_iterator entryIt = entryList.constBegin();
534 for ( ; entryIt != entryList.constEnd(); ++entryIt )
535 {
536 QStringList keyValueSplit = entryIt->split( ':' );
537 if ( keyValueSplit.size() < 2 )
538 {
539 continue;
540 }
541 const QString key = keyValueSplit.at( 0 );
542 QString value = keyValueSplit.at( 1 );
543 QString newValue = value;
544 value = value.trimmed().toLower();
545
546 if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
547 {
548 newValue = fill.name();
549 }
550 else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
551 {
552 newValue = QString::number( fill.alphaF() );
553 }
554 else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
555 {
556 newValue = stroke.name();
557 }
558 else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
559 {
560 newValue = QString::number( stroke.alphaF() );
561 }
562 else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
563 {
564 newValue = QString::number( strokeWidth );
565 }
566
567 if ( entryIt != entryList.constBegin() )
568 {
569 newAttributeString.append( ';' );
570 }
571 newAttributeString.append( key + ':' + newValue );
572 }
573 elem.setAttribute( attribute.name(), newAttributeString );
574 }
575 else
576 {
577 const QString value = attribute.value().trimmed().toLower();
578 if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
579 {
580 elem.setAttribute( attribute.name(), fill.name() );
581 }
582 else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
583 {
584 elem.setAttribute( attribute.name(), fill.alphaF() );
585 }
586 else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
587 {
588 elem.setAttribute( attribute.name(), stroke.name() );
589 }
590 else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
591 {
592 elem.setAttribute( attribute.name(), stroke.alphaF() );
593 }
594 else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
595 {
596 elem.setAttribute( attribute.name(), QString::number( strokeWidth ) );
597 }
598 }
599 }
600
601 QDomNodeList childList = elem.childNodes();
602 int nChildren = childList.count();
603 for ( int i = 0; i < nChildren; ++i )
604 {
605 QDomElement childElem = childList.at( i ).toElement();
606 replaceElemParams( childElem, fill, stroke, strokeWidth );
607 }
608 }
609
containsElemParams(const QDomElement & elem,bool & hasFillParam,bool & hasDefaultFill,QColor & defaultFill,bool & hasFillOpacityParam,bool & hasDefaultFillOpacity,double & defaultFillOpacity,bool & hasStrokeParam,bool & hasDefaultStroke,QColor & defaultStroke,bool & hasStrokeWidthParam,bool & hasDefaultStrokeWidth,double & defaultStrokeWidth,bool & hasStrokeOpacityParam,bool & hasDefaultStrokeOpacity,double & defaultStrokeOpacity) const610 void QgsSvgCache::containsElemParams( const QDomElement &elem, bool &hasFillParam, bool &hasDefaultFill, QColor &defaultFill,
611 bool &hasFillOpacityParam, bool &hasDefaultFillOpacity, double &defaultFillOpacity,
612 bool &hasStrokeParam, bool &hasDefaultStroke, QColor &defaultStroke,
613 bool &hasStrokeWidthParam, bool &hasDefaultStrokeWidth, double &defaultStrokeWidth,
614 bool &hasStrokeOpacityParam, bool &hasDefaultStrokeOpacity, double &defaultStrokeOpacity ) const
615 {
616 if ( elem.isNull() )
617 {
618 return;
619 }
620
621 //we already have all the information, no need to go deeper
622 if ( hasFillParam && hasStrokeParam && hasStrokeWidthParam && hasFillOpacityParam && hasStrokeOpacityParam )
623 {
624 return;
625 }
626
627 //check this elements attribute
628 QDomNamedNodeMap attributes = elem.attributes();
629 int nAttributes = attributes.count();
630
631 QStringList valueSplit;
632 for ( int i = 0; i < nAttributes; ++i )
633 {
634 QDomAttr attribute = attributes.item( i ).toAttr();
635 if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
636 {
637 //entries separated by ';'
638 QStringList entryList = attribute.value().split( ';' );
639 QStringList::const_iterator entryIt = entryList.constBegin();
640 for ( ; entryIt != entryList.constEnd(); ++entryIt )
641 {
642 QStringList keyValueSplit = entryIt->split( ':' );
643 if ( keyValueSplit.size() < 2 )
644 {
645 continue;
646 }
647 QString value = keyValueSplit.at( 1 );
648 valueSplit = value.split( ' ' );
649 if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
650 {
651 hasFillParam = true;
652 if ( valueSplit.size() > 1 )
653 {
654 defaultFill = QColor( valueSplit.at( 1 ) );
655 hasDefaultFill = true;
656 }
657 }
658 else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
659 {
660 hasFillOpacityParam = true;
661 if ( valueSplit.size() > 1 )
662 {
663 bool ok;
664 double opacity = valueSplit.at( 1 ).toDouble( &ok );
665 if ( ok )
666 {
667 defaultFillOpacity = opacity;
668 hasDefaultFillOpacity = true;
669 }
670 }
671 }
672 else if ( !hasStrokeParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
673 {
674 hasStrokeParam = true;
675 if ( valueSplit.size() > 1 )
676 {
677 defaultStroke = QColor( valueSplit.at( 1 ) );
678 hasDefaultStroke = true;
679 }
680 }
681 else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
682 {
683 hasStrokeWidthParam = true;
684 if ( valueSplit.size() > 1 )
685 {
686 defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
687 hasDefaultStrokeWidth = true;
688 }
689 }
690 else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
691 {
692 hasStrokeOpacityParam = true;
693 if ( valueSplit.size() > 1 )
694 {
695 bool ok;
696 double opacity = valueSplit.at( 1 ).toDouble( &ok );
697 if ( ok )
698 {
699 defaultStrokeOpacity = opacity;
700 hasDefaultStrokeOpacity = true;
701 }
702 }
703 }
704 }
705 }
706 else
707 {
708 QString value = attribute.value();
709 valueSplit = value.split( ' ' );
710 if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
711 {
712 hasFillParam = true;
713 if ( valueSplit.size() > 1 )
714 {
715 defaultFill = QColor( valueSplit.at( 1 ) );
716 hasDefaultFill = true;
717 }
718 }
719 else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
720 {
721 hasFillOpacityParam = true;
722 if ( valueSplit.size() > 1 )
723 {
724 bool ok;
725 double opacity = valueSplit.at( 1 ).toDouble( &ok );
726 if ( ok )
727 {
728 defaultFillOpacity = opacity;
729 hasDefaultFillOpacity = true;
730 }
731 }
732 }
733 else if ( !hasStrokeParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
734 {
735 hasStrokeParam = true;
736 if ( valueSplit.size() > 1 )
737 {
738 defaultStroke = QColor( valueSplit.at( 1 ) );
739 hasDefaultStroke = true;
740 }
741 }
742 else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
743 {
744 hasStrokeWidthParam = true;
745 if ( valueSplit.size() > 1 )
746 {
747 defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
748 hasDefaultStrokeWidth = true;
749 }
750 }
751 else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
752 {
753 hasStrokeOpacityParam = true;
754 if ( valueSplit.size() > 1 )
755 {
756 bool ok;
757 double opacity = valueSplit.at( 1 ).toDouble( &ok );
758 if ( ok )
759 {
760 defaultStrokeOpacity = opacity;
761 hasDefaultStrokeOpacity = true;
762 }
763 }
764 }
765 }
766 }
767
768 //pass it further to child items
769 QDomNodeList childList = elem.childNodes();
770 int nChildren = childList.count();
771 for ( int i = 0; i < nChildren; ++i )
772 {
773 QDomElement childElem = childList.at( i ).toElement();
774 containsElemParams( childElem, hasFillParam, hasDefaultFill, defaultFill,
775 hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
776 hasStrokeParam, hasDefaultStroke, defaultStroke,
777 hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
778 hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
779 }
780 }
781
sizeForImage(const QgsSvgCacheEntry & entry,QSizeF & viewBoxSize,QSizeF & scaledSize) const782 QSize QgsSvgCache::sizeForImage( const QgsSvgCacheEntry &entry, QSizeF &viewBoxSize, QSizeF &scaledSize ) const
783 {
784 bool isFixedAR = entry.fixedAspectRatio > 0;
785
786 QSvgRenderer r( entry.svgContent );
787 double hwRatio = 1.0;
788 viewBoxSize = r.viewBoxF().size();
789 if ( viewBoxSize.width() > 0 )
790 {
791 if ( isFixedAR )
792 {
793 hwRatio = entry.fixedAspectRatio;
794 }
795 else
796 {
797 hwRatio = viewBoxSize.height() / viewBoxSize.width();
798 }
799 }
800
801 // cast double image sizes to int for QImage
802 scaledSize.setWidth( entry.size );
803 int wImgSize = static_cast< int >( scaledSize.width() );
804 if ( wImgSize < 1 )
805 {
806 wImgSize = 1;
807 }
808 scaledSize.setHeight( scaledSize.width() * hwRatio );
809 int hImgSize = static_cast< int >( scaledSize.height() );
810 if ( hImgSize < 1 )
811 {
812 hImgSize = 1;
813 }
814 return QSize( wImgSize, hImgSize );
815 }
816
imageFromCachedPicture(const QgsSvgCacheEntry & entry) const817 QImage QgsSvgCache::imageFromCachedPicture( const QgsSvgCacheEntry &entry ) const
818 {
819 QSizeF viewBoxSize;
820 QSizeF scaledSize;
821 QImage image( sizeForImage( entry, viewBoxSize, scaledSize ), QImage::Format_ARGB32_Premultiplied );
822 image.fill( 0 ); // transparent background
823
824 QPainter p( &image );
825 p.drawPicture( QPoint( 0, 0 ), *entry.picture );
826 return image;
827 }
828
829