Branch data Line data Source code
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 <QRegularExpression>
36 : : #include <QSvgRenderer>
37 : : #include <QFileInfo>
38 : : #include <QNetworkReply>
39 : : #include <QNetworkRequest>
40 : :
41 : : ///@cond PRIVATE
42 : :
43 : : //
44 : : // QgsSvgCacheEntry
45 : : //
46 : :
47 : 40 : QgsSvgCacheEntry::QgsSvgCacheEntry( const QString &path, double size, double strokeWidth, double widthScaleFactor, const QColor &fill, const QColor &stroke, double fixedAspectRatio, const QMap<QString, QString> ¶meters )
48 : 40 : : QgsAbstractContentCacheEntry( path )
49 : 40 : , size( size )
50 : 40 : , strokeWidth( strokeWidth )
51 : 40 : , widthScaleFactor( widthScaleFactor )
52 : 40 : , fixedAspectRatio( fixedAspectRatio )
53 : 40 : , fill( fill )
54 : 40 : , stroke( stroke )
55 : 40 : , parameters( parameters )
56 : 80 : {
57 : 40 : }
58 : :
59 : 15 : bool QgsSvgCacheEntry::isEqual( const QgsAbstractContentCacheEntry *other ) const
60 : : {
61 : 15 : const QgsSvgCacheEntry *otherSvg = dynamic_cast< const QgsSvgCacheEntry * >( other );
62 : : // cheapest checks first!
63 : 25 : if ( !otherSvg
64 : 15 : || !qgsDoubleNear( otherSvg->fixedAspectRatio, fixedAspectRatio )
65 : 15 : || !qgsDoubleNear( otherSvg->size, size )
66 : 15 : || !qgsDoubleNear( otherSvg->strokeWidth, strokeWidth )
67 : 10 : || !qgsDoubleNear( otherSvg->widthScaleFactor, widthScaleFactor )
68 : 10 : || otherSvg->fill != fill
69 : 10 : || otherSvg->stroke != stroke
70 : 10 : || otherSvg->path != path
71 : 10 : || otherSvg->parameters != parameters )
72 : 5 : return false;
73 : :
74 : 10 : return true;
75 : 15 : }
76 : :
77 : 0 : int QgsSvgCacheEntry::dataSize() const
78 : : {
79 : 0 : int size = svgContent.size();
80 : 0 : if ( picture )
81 : : {
82 : 0 : size += picture->size();
83 : 0 : }
84 : 0 : if ( image )
85 : : {
86 : 40 : size += ( image->width() * image->height() * 32 );
87 : 0 : }
88 : 0 : return size;
89 : : }
90 : :
91 : 0 : void QgsSvgCacheEntry::dump() const
92 : : {
93 : 0 : QgsDebugMsgLevel( QStringLiteral( "path: %1, size %2, width scale factor %3" ).arg( path ).arg( size ).arg( widthScaleFactor ), 4 );
94 : 0 : }
95 : : ///@endcond
96 : :
97 : :
98 : : //
99 : : // QgsSvgCache
100 : : //
101 : :
102 : 10 : QgsSvgCache::QgsSvgCache( QObject *parent )
103 : 5 : : QgsAbstractContentCache< QgsSvgCacheEntry >( parent, QObject::tr( "SVG" ) )
104 : 5 : {
105 : 10 : mMissingSvg = QStringLiteral( "<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
106 : :
107 : 10 : const QString downloadingSvgPath = QgsApplication::defaultThemePath() + QStringLiteral( "downloading_svg.svg" );
108 : 5 : if ( QFile::exists( downloadingSvgPath ) )
109 : : {
110 : 5 : QFile file( downloadingSvgPath );
111 : 5 : if ( file.open( QIODevice::ReadOnly ) )
112 : : {
113 : 5 : mFetchingSvg = file.readAll();
114 : 5 : }
115 : 5 : }
116 : :
117 : 5 : if ( mFetchingSvg.isEmpty() )
118 : : {
119 : 0 : mFetchingSvg = QStringLiteral( "<svg width='10' height='10'><text x='5' y='10' font-size='10' text-anchor='middle'>?</text></svg>" ).toLatin1();
120 : 0 : }
121 : :
122 : 5 : connect( this, &QgsAbstractContentCacheBase::remoteContentFetched, this, &QgsSvgCache::remoteSvgFetched );
123 : 5 : }
124 : :
125 : 0 : QImage QgsSvgCache::svgAsImage( const QString &file, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
126 : : double widthScaleFactor, bool &fitsInCache, double fixedAspectRatio, bool blocking, const QMap<QString, QString> ¶meters )
127 : : {
128 : 0 : QMutexLocker locker( &mMutex );
129 : :
130 : 0 : fitsInCache = true;
131 : 0 : QgsSvgCacheEntry *currentEntry = cacheEntry( file, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, parameters, blocking );
132 : :
133 : 0 : QImage result;
134 : :
135 : : //if current entry image is 0: cache image for entry
136 : : // checks to see if image will fit into cache
137 : : //update stats for memory usage
138 : 0 : if ( !currentEntry->image )
139 : : {
140 : 0 : QSvgRenderer r( currentEntry->svgContent );
141 : 0 : double hwRatio = 1.0;
142 : 0 : if ( r.viewBoxF().width() > 0 )
143 : : {
144 : 0 : if ( currentEntry->fixedAspectRatio > 0 )
145 : : {
146 : 0 : hwRatio = currentEntry->fixedAspectRatio;
147 : 0 : }
148 : : else
149 : : {
150 : 0 : hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
151 : : }
152 : 0 : }
153 : 0 : long cachedDataSize = 0;
154 : 0 : cachedDataSize += currentEntry->svgContent.size();
155 : 0 : cachedDataSize += static_cast< int >( currentEntry->size * currentEntry->size * hwRatio * 32 );
156 : 0 : if ( cachedDataSize > mMaxCacheSize / 2 )
157 : : {
158 : 0 : fitsInCache = false;
159 : 0 : currentEntry->image.reset();
160 : :
161 : : // instead cache picture
162 : 0 : if ( !currentEntry->picture )
163 : : {
164 : 0 : cachePicture( currentEntry, false );
165 : 0 : }
166 : :
167 : : // ...and render cached picture to result image
168 : 0 : result = imageFromCachedPicture( *currentEntry );
169 : 0 : }
170 : : else
171 : : {
172 : 0 : cacheImage( currentEntry );
173 : 0 : result = *( currentEntry->image );
174 : : }
175 : 0 : trimToMaximumSize();
176 : 0 : }
177 : : else
178 : : {
179 : 0 : result = *( currentEntry->image );
180 : : }
181 : :
182 : 0 : return result;
183 : 0 : }
184 : :
185 : 0 : QPicture QgsSvgCache::svgAsPicture( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
186 : : double widthScaleFactor, bool forceVectorOutput, double fixedAspectRatio, bool blocking, const QMap<QString, QString> ¶meters )
187 : : {
188 : 0 : QMutexLocker locker( &mMutex );
189 : :
190 : 0 : QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, parameters, blocking );
191 : :
192 : : //if current entry picture is 0: cache picture for entry
193 : : //update stats for memory usage
194 : 0 : if ( !currentEntry->picture )
195 : : {
196 : 0 : cachePicture( currentEntry, forceVectorOutput );
197 : 0 : trimToMaximumSize();
198 : 0 : }
199 : :
200 : 0 : QPicture p;
201 : : // For some reason p.detach() doesn't seem to always work as intended, at
202 : : // least with QT 5.5 on Ubuntu 16.04
203 : : // Serialization/deserialization is a safe way to be ensured we don't
204 : : // share a copy.
205 : 0 : p.setData( currentEntry->picture->data(), currentEntry->picture->size() );
206 : 0 : return p;
207 : 0 : }
208 : :
209 : 0 : QByteArray QgsSvgCache::svgContent( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
210 : : double widthScaleFactor, double fixedAspectRatio, bool blocking, const QMap<QString, QString> ¶meters, bool *isMissingImage )
211 : : {
212 : 0 : QMutexLocker locker( &mMutex );
213 : :
214 : 0 : QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, parameters, blocking, isMissingImage );
215 : :
216 : 0 : return currentEntry->svgContent;
217 : 0 : }
218 : :
219 : 40 : QSizeF QgsSvgCache::svgViewboxSize( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
220 : : double widthScaleFactor, double fixedAspectRatio, bool blocking, const QMap<QString, QString> ¶meters )
221 : : {
222 : 40 : QMutexLocker locker( &mMutex );
223 : :
224 : 40 : QgsSvgCacheEntry *currentEntry = cacheEntry( path, size, fill, stroke, strokeWidth, widthScaleFactor, fixedAspectRatio, parameters, blocking );
225 : 40 : return currentEntry->viewboxSize;
226 : 40 : }
227 : :
228 : 0 : void QgsSvgCache::containsParams( const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasStrokeParam, QColor &defaultStrokeColor,
229 : : bool &hasStrokeWidthParam, double &defaultStrokeWidth, bool blocking ) const
230 : : {
231 : 0 : bool hasDefaultFillColor = false;
232 : 0 : bool hasFillOpacityParam = false;
233 : 0 : bool hasDefaultFillOpacity = false;
234 : 0 : double defaultFillOpacity = 1.0;
235 : 0 : bool hasDefaultStrokeColor = false;
236 : 0 : bool hasDefaultStrokeWidth = false;
237 : 0 : bool hasStrokeOpacityParam = false;
238 : 0 : bool hasDefaultStrokeOpacity = false;
239 : 0 : double defaultStrokeOpacity = 1.0;
240 : :
241 : 0 : containsParams( path, hasFillParam, hasDefaultFillColor, defaultFillColor,
242 : : hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
243 : 0 : hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
244 : 0 : hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
245 : : hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity,
246 : 0 : blocking );
247 : 0 : }
248 : :
249 : 40 : void QgsSvgCache::containsParams( const QString &path,
250 : : bool &hasFillParam, bool &hasDefaultFillParam, QColor &defaultFillColor,
251 : : bool &hasFillOpacityParam, bool &hasDefaultFillOpacity, double &defaultFillOpacity,
252 : : bool &hasStrokeParam, bool &hasDefaultStrokeColor, QColor &defaultStrokeColor,
253 : : bool &hasStrokeWidthParam, bool &hasDefaultStrokeWidth, double &defaultStrokeWidth,
254 : : bool &hasStrokeOpacityParam, bool &hasDefaultStrokeOpacity, double &defaultStrokeOpacity,
255 : : bool blocking ) const
256 : : {
257 : 40 : hasFillParam = false;
258 : 40 : hasFillOpacityParam = false;
259 : 40 : hasStrokeParam = false;
260 : 40 : hasStrokeWidthParam = false;
261 : 40 : hasStrokeOpacityParam = false;
262 : 40 : defaultFillColor = QColor( Qt::white );
263 : 40 : defaultFillOpacity = 1.0;
264 : 40 : defaultStrokeColor = QColor( Qt::black );
265 : 40 : defaultStrokeWidth = 0.2;
266 : 40 : defaultStrokeOpacity = 1.0;
267 : :
268 : 40 : hasDefaultFillParam = false;
269 : 40 : hasDefaultFillOpacity = false;
270 : 40 : hasDefaultStrokeColor = false;
271 : 40 : hasDefaultStrokeWidth = false;
272 : 40 : hasDefaultStrokeOpacity = false;
273 : :
274 : 40 : QDomDocument svgDoc;
275 : 40 : if ( !svgDoc.setContent( getContent( path, mMissingSvg, mFetchingSvg, blocking ) ) )
276 : : {
277 : 0 : return;
278 : : }
279 : :
280 : 40 : QDomElement docElem = svgDoc.documentElement();
281 : 80 : containsElemParams( docElem, hasFillParam, hasDefaultFillParam, defaultFillColor,
282 : 40 : hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
283 : 40 : hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
284 : 40 : hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
285 : 40 : hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
286 : 40 : }
287 : :
288 : 30 : void QgsSvgCache::replaceParamsAndCacheSvg( QgsSvgCacheEntry *entry, bool blocking )
289 : : {
290 : 30 : if ( !entry )
291 : : {
292 : 0 : return;
293 : : }
294 : :
295 : 30 : const QByteArray content = getContent( entry->path, mMissingSvg, mFetchingSvg, blocking ) ;
296 : 30 : entry->isMissingImage = content == mMissingSvg;
297 : 30 : QDomDocument svgDoc;
298 : 30 : if ( !svgDoc.setContent( content ) )
299 : : {
300 : 0 : return;
301 : : }
302 : :
303 : : //replace fill color, stroke color, stroke with in all nodes
304 : 30 : QDomElement docElem = svgDoc.documentElement();
305 : :
306 : 30 : QSizeF viewboxSize;
307 : 30 : double sizeScaleFactor = calcSizeScaleFactor( entry, docElem, viewboxSize );
308 : 30 : entry->viewboxSize = viewboxSize;
309 : 30 : replaceElemParams( docElem, entry->fill, entry->stroke, entry->strokeWidth * sizeScaleFactor, entry->parameters );
310 : :
311 : 30 : entry->svgContent = svgDoc.toByteArray( 0 );
312 : :
313 : :
314 : : // toByteArray screws up tspans inside text by adding new lines before and after each span... this should help, at the
315 : : // risk of potentially breaking some svgs where the newline is desired
316 : 30 : entry->svgContent.replace( "\n<tspan", "<tspan" );
317 : 30 : entry->svgContent.replace( "</tspan>\n", "</tspan>" );
318 : :
319 : 30 : mTotalSize += entry->svgContent.size();
320 : 30 : }
321 : :
322 : 30 : double QgsSvgCache::calcSizeScaleFactor( QgsSvgCacheEntry *entry, const QDomElement &docElem, QSizeF &viewboxSize ) const
323 : : {
324 : 30 : QString viewBox;
325 : :
326 : : //bad size
327 : 30 : if ( !entry || qgsDoubleNear( entry->size, 0.0 ) )
328 : 0 : return 1.0;
329 : :
330 : : //find svg viewbox attribute
331 : : //first check if docElem is svg element
332 : 90 : if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "viewBox" ) ) )
333 : : {
334 : 0 : viewBox = docElem.attribute( QStringLiteral( "viewBox" ), QString() );
335 : 0 : }
336 : 90 : else if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "viewbox" ) ) )
337 : : {
338 : 0 : viewBox = docElem.attribute( QStringLiteral( "viewbox" ), QString() );
339 : 0 : }
340 : : else
341 : : {
342 : 60 : QDomElement svgElem = docElem.firstChildElement( QStringLiteral( "svg" ) );
343 : 30 : if ( !svgElem.isNull() )
344 : : {
345 : 0 : if ( svgElem.hasAttribute( QStringLiteral( "viewBox" ) ) )
346 : 0 : viewBox = svgElem.attribute( QStringLiteral( "viewBox" ), QString() );
347 : 0 : else if ( svgElem.hasAttribute( QStringLiteral( "viewbox" ) ) )
348 : 0 : viewBox = svgElem.attribute( QStringLiteral( "viewbox" ), QString() );
349 : 0 : }
350 : 30 : }
351 : :
352 : : //could not find valid viewbox attribute
353 : 30 : if ( viewBox.isEmpty() )
354 : : {
355 : : // trying looking for width/height and use them as a fallback
356 : 90 : if ( docElem.tagName() == QLatin1String( "svg" ) && docElem.hasAttribute( QStringLiteral( "width" ) ) )
357 : : {
358 : 60 : const QString widthString = docElem.attribute( QStringLiteral( "width" ) );
359 : 60 : const QRegularExpression measureRegEx( QStringLiteral( "([\\d\\.]+).*?$" ) );
360 : 30 : const QRegularExpressionMatch widthMatch = measureRegEx.match( widthString );
361 : 30 : if ( widthMatch.hasMatch() )
362 : : {
363 : 30 : double width = widthMatch.captured( 1 ).toDouble();
364 : 60 : const QString heightString = docElem.attribute( QStringLiteral( "height" ) );
365 : :
366 : 30 : const QRegularExpressionMatch heightMatch = measureRegEx.match( heightString );
367 : 30 : if ( heightMatch.hasMatch() )
368 : : {
369 : 30 : double height = heightMatch.captured( 1 ).toDouble();
370 : 30 : viewboxSize = QSizeF( width, height );
371 : 30 : return width / entry->size;
372 : : }
373 : 30 : }
374 : 30 : }
375 : :
376 : 0 : return 1.0;
377 : : }
378 : :
379 : :
380 : : //width should be 3rd element in a 4 part space delimited string
381 : 0 : QStringList parts = viewBox.split( ' ' );
382 : 0 : if ( parts.count() != 4 )
383 : 0 : return 1.0;
384 : :
385 : 0 : bool heightOk = false;
386 : 0 : double height = parts.at( 3 ).toDouble( &heightOk );
387 : :
388 : 0 : bool widthOk = false;
389 : 0 : double width = parts.at( 2 ).toDouble( &widthOk );
390 : 0 : if ( widthOk )
391 : : {
392 : 0 : if ( heightOk )
393 : 0 : viewboxSize = QSizeF( width, height );
394 : 0 : return width / entry->size;
395 : : }
396 : :
397 : 0 : return 1.0;
398 : 30 : }
399 : :
400 : :
401 : 0 : QByteArray QgsSvgCache::getImageData( const QString &path, bool blocking ) const
402 : : {
403 : 0 : return getContent( path, mMissingSvg, mFetchingSvg, blocking );
404 : : };
405 : :
406 : 0 : bool QgsSvgCache::checkReply( QNetworkReply *reply, const QString &path ) const
407 : : {
408 : : // we accept both real SVG mime types AND plain text types - because some sites
409 : : // (notably github) serve up svgs as raw text
410 : 0 : QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
411 : 0 : if ( !contentType.startsWith( QLatin1String( "image/svg+xml" ), Qt::CaseInsensitive )
412 : 0 : && !contentType.startsWith( QLatin1String( "text/plain" ), Qt::CaseInsensitive ) )
413 : : {
414 : 0 : QgsMessageLog::logMessage( tr( "Unexpected MIME type %1 received for %2" ).arg( contentType, path ), tr( "SVG" ) );
415 : 0 : return false;
416 : : }
417 : 0 : return true;
418 : 0 : }
419 : :
420 : 0 : void QgsSvgCache::cacheImage( QgsSvgCacheEntry *entry )
421 : : {
422 : 0 : if ( !entry )
423 : : {
424 : 0 : return;
425 : : }
426 : :
427 : 0 : entry->image.reset();
428 : :
429 : 0 : QSizeF viewBoxSize;
430 : 0 : QSizeF scaledSize;
431 : 0 : QSize imageSize = sizeForImage( *entry, viewBoxSize, scaledSize );
432 : :
433 : : // cast double image sizes to int for QImage
434 : 0 : std::unique_ptr< QImage > image = std::make_unique< QImage >( imageSize, QImage::Format_ARGB32_Premultiplied );
435 : 0 : image->fill( 0 ); // transparent background
436 : :
437 : 0 : const bool isFixedAR = entry->fixedAspectRatio > 0;
438 : :
439 : 0 : QPainter p( image.get() );
440 : 0 : QSvgRenderer r( entry->svgContent );
441 : 0 : if ( qgsDoubleNear( viewBoxSize.width(), viewBoxSize.height() ) )
442 : : {
443 : 0 : r.render( &p );
444 : 0 : }
445 : : else
446 : : {
447 : 0 : QSizeF s( viewBoxSize );
448 : 0 : s.scale( scaledSize.width(), scaledSize.height(), isFixedAR ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio );
449 : 0 : QRectF rect( ( imageSize.width() - s.width() ) / 2, ( imageSize.height() - s.height() ) / 2, s.width(), s.height() );
450 : 0 : r.render( &p, rect );
451 : : }
452 : :
453 : 0 : mTotalSize += ( image->width() * image->height() * 32 );
454 : 0 : entry->image = std::move( image );
455 : 0 : }
456 : :
457 : 0 : void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry, bool forceVectorOutput )
458 : : {
459 : : Q_UNUSED( forceVectorOutput )
460 : 0 : if ( !entry )
461 : : {
462 : 0 : return;
463 : : }
464 : :
465 : 0 : entry->picture.reset();
466 : :
467 : 0 : bool isFixedAR = entry->fixedAspectRatio > 0;
468 : :
469 : : //correct QPictures dpi correction
470 : 0 : std::unique_ptr< QPicture > picture = std::make_unique< QPicture >();
471 : 0 : QRectF rect;
472 : 0 : QSvgRenderer r( entry->svgContent );
473 : 0 : double hwRatio = 1.0;
474 : 0 : if ( r.viewBoxF().width() > 0 )
475 : : {
476 : 0 : if ( isFixedAR )
477 : : {
478 : 0 : hwRatio = entry->fixedAspectRatio;
479 : 0 : }
480 : : else
481 : : {
482 : 0 : hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
483 : : }
484 : 0 : }
485 : :
486 : 0 : double wSize = entry->size;
487 : 0 : double hSize = wSize * hwRatio;
488 : :
489 : 0 : QSizeF s( r.viewBoxF().size() );
490 : 0 : s.scale( wSize, hSize, isFixedAR ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio );
491 : 0 : rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
492 : :
493 : 0 : QPainter p( picture.get() );
494 : 0 : r.render( &p, rect );
495 : 0 : entry->picture = std::move( picture );
496 : 0 : mTotalSize += entry->picture->size();
497 : 0 : }
498 : :
499 : 40 : QgsSvgCacheEntry *QgsSvgCache::cacheEntry( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
500 : : double widthScaleFactor, double fixedAspectRatio, const QMap<QString, QString> ¶meters, bool blocking, bool *isMissingImage )
501 : : {
502 : 40 : QgsSvgCacheEntry *currentEntry = findExistingEntry( new QgsSvgCacheEntry( path, size, strokeWidth, widthScaleFactor, fill, stroke, fixedAspectRatio, parameters ) );
503 : :
504 : 40 : if ( currentEntry->svgContent.isEmpty() )
505 : : {
506 : 30 : replaceParamsAndCacheSvg( currentEntry, blocking );
507 : 30 : }
508 : :
509 : 40 : if ( isMissingImage )
510 : 0 : *isMissingImage = currentEntry->isMissingImage;
511 : :
512 : 40 : return currentEntry;
513 : 0 : }
514 : :
515 : :
516 : 90 : void QgsSvgCache::replaceElemParams( QDomElement &elem, const QColor &fill, const QColor &stroke, double strokeWidth, const QMap<QString, QString> ¶meters )
517 : : {
518 : 90 : if ( elem.isNull() )
519 : : {
520 : 30 : return;
521 : : }
522 : :
523 : : //go through attributes
524 : 60 : QDomNamedNodeMap attributes = elem.attributes();
525 : 60 : int nAttributes = attributes.count();
526 : 240 : for ( int i = 0; i < nAttributes; ++i )
527 : : {
528 : 180 : QDomAttr attribute = attributes.item( i ).toAttr();
529 : : //e.g. style="fill:param(fill);param(stroke)"
530 : 180 : if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
531 : : {
532 : : //entries separated by ';'
533 : 0 : QString newAttributeString;
534 : :
535 : 0 : QStringList entryList = attribute.value().split( ';' );
536 : 0 : QStringList::const_iterator entryIt = entryList.constBegin();
537 : 0 : for ( ; entryIt != entryList.constEnd(); ++entryIt )
538 : : {
539 : 0 : QStringList keyValueSplit = entryIt->split( ':' );
540 : 0 : if ( keyValueSplit.size() < 2 )
541 : : {
542 : 0 : continue;
543 : : }
544 : 0 : const QString key = keyValueSplit.at( 0 );
545 : 0 : QString value = keyValueSplit.at( 1 );
546 : 0 : QString newValue = value;
547 : 0 : value = value.trimmed().toLower();
548 : :
549 : 0 : if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
550 : : {
551 : 0 : newValue = fill.name();
552 : 0 : }
553 : 0 : else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
554 : : {
555 : 0 : newValue = QString::number( fill.alphaF() );
556 : 0 : }
557 : 0 : else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
558 : : {
559 : 0 : newValue = stroke.name();
560 : 0 : }
561 : 0 : else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
562 : : {
563 : 0 : newValue = QString::number( stroke.alphaF() );
564 : 0 : }
565 : 0 : else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
566 : : {
567 : 0 : newValue = QString::number( strokeWidth );
568 : 0 : }
569 : :
570 : 0 : if ( entryIt != entryList.constBegin() )
571 : : {
572 : 0 : newAttributeString.append( ';' );
573 : 0 : }
574 : 0 : newAttributeString.append( key + ':' + newValue );
575 : 0 : }
576 : 0 : elem.setAttribute( attribute.name(), newAttributeString );
577 : 0 : }
578 : : else
579 : : {
580 : 180 : const QString value = attribute.value().trimmed().toLower();
581 : 180 : if ( value.startsWith( QLatin1String( "param(fill)" ) ) )
582 : : {
583 : 0 : elem.setAttribute( attribute.name(), fill.name() );
584 : 0 : }
585 : 180 : else if ( value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
586 : : {
587 : 0 : elem.setAttribute( attribute.name(), fill.alphaF() );
588 : 0 : }
589 : 180 : else if ( value.startsWith( QLatin1String( "param(outline)" ) ) )
590 : : {
591 : 0 : elem.setAttribute( attribute.name(), stroke.name() );
592 : 0 : }
593 : 180 : else if ( value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
594 : : {
595 : 0 : elem.setAttribute( attribute.name(), stroke.alphaF() );
596 : 0 : }
597 : 180 : else if ( value.startsWith( QLatin1String( "param(outline-width)" ) ) )
598 : : {
599 : 0 : elem.setAttribute( attribute.name(), QString::number( strokeWidth ) );
600 : 0 : }
601 : : else
602 : : {
603 : 180 : QMap<QString, QString>::const_iterator paramIt = parameters.constBegin();
604 : 180 : for ( ; paramIt != parameters.constEnd(); ++paramIt )
605 : : {
606 : 0 : if ( value.startsWith( QString( QLatin1String( "param(%1)" ) ).arg( paramIt.key() ) ) )
607 : : {
608 : 0 : elem.setAttribute( attribute.name(), paramIt.value() );
609 : 0 : break;
610 : : }
611 : 0 : }
612 : : }
613 : 180 : }
614 : 180 : }
615 : :
616 : 60 : QDomNode child = elem.firstChild();
617 : 90 : if ( child.isText() && child.nodeValue().startsWith( "param(" ) )
618 : : {
619 : 0 : QMap<QString, QString>::const_iterator paramIt = parameters.constBegin();
620 : 0 : for ( ; paramIt != parameters.constEnd(); ++paramIt )
621 : : {
622 : 0 : if ( child.toText().data().startsWith( QString( QLatin1String( "param(%1)" ) ).arg( paramIt.key() ) ) )
623 : : {
624 : 0 : child.setNodeValue( paramIt.value() );
625 : 0 : break;
626 : : }
627 : 0 : }
628 : 0 : }
629 : :
630 : 60 : QDomNodeList childList = elem.childNodes();
631 : 60 : int nChildren = childList.count();
632 : 120 : for ( int i = 0; i < nChildren; ++i )
633 : : {
634 : 60 : QDomElement childElem = childList.at( i ).toElement();
635 : 60 : replaceElemParams( childElem, fill, stroke, strokeWidth, parameters );
636 : 60 : }
637 : 90 : }
638 : :
639 : 120 : void QgsSvgCache::containsElemParams( const QDomElement &elem, bool &hasFillParam, bool &hasDefaultFill, QColor &defaultFill,
640 : : bool &hasFillOpacityParam, bool &hasDefaultFillOpacity, double &defaultFillOpacity,
641 : : bool &hasStrokeParam, bool &hasDefaultStroke, QColor &defaultStroke,
642 : : bool &hasStrokeWidthParam, bool &hasDefaultStrokeWidth, double &defaultStrokeWidth,
643 : : bool &hasStrokeOpacityParam, bool &hasDefaultStrokeOpacity, double &defaultStrokeOpacity ) const
644 : : {
645 : 120 : if ( elem.isNull() )
646 : : {
647 : 40 : return;
648 : : }
649 : :
650 : : //we already have all the information, no need to go deeper
651 : 80 : if ( hasFillParam && hasStrokeParam && hasStrokeWidthParam && hasFillOpacityParam && hasStrokeOpacityParam )
652 : : {
653 : 0 : return;
654 : : }
655 : :
656 : : //check this elements attribute
657 : 80 : QDomNamedNodeMap attributes = elem.attributes();
658 : 80 : int nAttributes = attributes.count();
659 : :
660 : 80 : QStringList valueSplit;
661 : 320 : for ( int i = 0; i < nAttributes; ++i )
662 : : {
663 : 240 : QDomAttr attribute = attributes.item( i ).toAttr();
664 : 240 : if ( attribute.name().compare( QLatin1String( "style" ), Qt::CaseInsensitive ) == 0 )
665 : : {
666 : : //entries separated by ';'
667 : 0 : QStringList entryList = attribute.value().split( ';' );
668 : 0 : QStringList::const_iterator entryIt = entryList.constBegin();
669 : 0 : for ( ; entryIt != entryList.constEnd(); ++entryIt )
670 : : {
671 : 0 : QStringList keyValueSplit = entryIt->split( ':' );
672 : 0 : if ( keyValueSplit.size() < 2 )
673 : : {
674 : 0 : continue;
675 : : }
676 : 0 : QString value = keyValueSplit.at( 1 );
677 : 0 : valueSplit = value.split( ' ' );
678 : 0 : if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
679 : : {
680 : 0 : hasFillParam = true;
681 : 0 : if ( valueSplit.size() > 1 )
682 : : {
683 : 0 : defaultFill = QColor( valueSplit.at( 1 ) );
684 : 0 : hasDefaultFill = true;
685 : 0 : }
686 : 0 : }
687 : 0 : else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
688 : : {
689 : 0 : hasFillOpacityParam = true;
690 : 0 : if ( valueSplit.size() > 1 )
691 : : {
692 : : bool ok;
693 : 0 : double opacity = valueSplit.at( 1 ).toDouble( &ok );
694 : 0 : if ( ok )
695 : : {
696 : 0 : defaultFillOpacity = opacity;
697 : 0 : hasDefaultFillOpacity = true;
698 : 0 : }
699 : 0 : }
700 : 0 : }
701 : 0 : else if ( !hasStrokeParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
702 : : {
703 : 0 : hasStrokeParam = true;
704 : 0 : if ( valueSplit.size() > 1 )
705 : : {
706 : 0 : defaultStroke = QColor( valueSplit.at( 1 ) );
707 : 0 : hasDefaultStroke = true;
708 : 0 : }
709 : 0 : }
710 : 0 : else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
711 : : {
712 : 0 : hasStrokeWidthParam = true;
713 : 0 : if ( valueSplit.size() > 1 )
714 : : {
715 : 0 : defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
716 : 0 : hasDefaultStrokeWidth = true;
717 : 0 : }
718 : 0 : }
719 : 0 : else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
720 : : {
721 : 0 : hasStrokeOpacityParam = true;
722 : 0 : if ( valueSplit.size() > 1 )
723 : : {
724 : : bool ok;
725 : 0 : double opacity = valueSplit.at( 1 ).toDouble( &ok );
726 : 0 : if ( ok )
727 : : {
728 : 0 : defaultStrokeOpacity = opacity;
729 : 0 : hasDefaultStrokeOpacity = true;
730 : 0 : }
731 : 0 : }
732 : 0 : }
733 : 0 : }
734 : 0 : }
735 : : else
736 : : {
737 : 240 : QString value = attribute.value();
738 : 240 : valueSplit = value.split( ' ' );
739 : 240 : if ( !hasFillParam && value.startsWith( QLatin1String( "param(fill)" ) ) )
740 : : {
741 : 0 : hasFillParam = true;
742 : 0 : if ( valueSplit.size() > 1 )
743 : : {
744 : 0 : defaultFill = QColor( valueSplit.at( 1 ) );
745 : 0 : hasDefaultFill = true;
746 : 0 : }
747 : 0 : }
748 : 240 : else if ( !hasFillOpacityParam && value.startsWith( QLatin1String( "param(fill-opacity)" ) ) )
749 : : {
750 : 0 : hasFillOpacityParam = true;
751 : 0 : if ( valueSplit.size() > 1 )
752 : : {
753 : : bool ok;
754 : 0 : double opacity = valueSplit.at( 1 ).toDouble( &ok );
755 : 0 : if ( ok )
756 : : {
757 : 0 : defaultFillOpacity = opacity;
758 : 0 : hasDefaultFillOpacity = true;
759 : 0 : }
760 : 0 : }
761 : 0 : }
762 : 240 : else if ( !hasStrokeParam && value.startsWith( QLatin1String( "param(outline)" ) ) )
763 : : {
764 : 0 : hasStrokeParam = true;
765 : 0 : if ( valueSplit.size() > 1 )
766 : : {
767 : 0 : defaultStroke = QColor( valueSplit.at( 1 ) );
768 : 0 : hasDefaultStroke = true;
769 : 0 : }
770 : 0 : }
771 : 240 : else if ( !hasStrokeWidthParam && value.startsWith( QLatin1String( "param(outline-width)" ) ) )
772 : : {
773 : 0 : hasStrokeWidthParam = true;
774 : 0 : if ( valueSplit.size() > 1 )
775 : : {
776 : 0 : defaultStrokeWidth = valueSplit.at( 1 ).toDouble();
777 : 0 : hasDefaultStrokeWidth = true;
778 : 0 : }
779 : 0 : }
780 : 240 : else if ( !hasStrokeOpacityParam && value.startsWith( QLatin1String( "param(outline-opacity)" ) ) )
781 : : {
782 : 0 : hasStrokeOpacityParam = true;
783 : 0 : if ( valueSplit.size() > 1 )
784 : : {
785 : : bool ok;
786 : 0 : double opacity = valueSplit.at( 1 ).toDouble( &ok );
787 : 0 : if ( ok )
788 : : {
789 : 0 : defaultStrokeOpacity = opacity;
790 : 0 : hasDefaultStrokeOpacity = true;
791 : 0 : }
792 : 0 : }
793 : 0 : }
794 : 240 : }
795 : 240 : }
796 : :
797 : : //pass it further to child items
798 : 80 : QDomNodeList childList = elem.childNodes();
799 : 80 : int nChildren = childList.count();
800 : 160 : for ( int i = 0; i < nChildren; ++i )
801 : : {
802 : 80 : QDomElement childElem = childList.at( i ).toElement();
803 : 160 : containsElemParams( childElem, hasFillParam, hasDefaultFill, defaultFill,
804 : 80 : hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
805 : 80 : hasStrokeParam, hasDefaultStroke, defaultStroke,
806 : 80 : hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
807 : 80 : hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
808 : 80 : }
809 : 120 : }
810 : :
811 : 0 : QSize QgsSvgCache::sizeForImage( const QgsSvgCacheEntry &entry, QSizeF &viewBoxSize, QSizeF &scaledSize ) const
812 : : {
813 : 0 : bool isFixedAR = entry.fixedAspectRatio > 0;
814 : :
815 : 0 : QSvgRenderer r( entry.svgContent );
816 : 0 : double hwRatio = 1.0;
817 : 0 : viewBoxSize = r.viewBoxF().size();
818 : 0 : if ( viewBoxSize.width() > 0 )
819 : : {
820 : 0 : if ( isFixedAR )
821 : : {
822 : 0 : hwRatio = entry.fixedAspectRatio;
823 : 0 : }
824 : : else
825 : : {
826 : 0 : hwRatio = viewBoxSize.height() / viewBoxSize.width();
827 : : }
828 : 0 : }
829 : :
830 : : // cast double image sizes to int for QImage
831 : 0 : scaledSize.setWidth( entry.size );
832 : 0 : int wImgSize = static_cast< int >( scaledSize.width() );
833 : 0 : if ( wImgSize < 1 )
834 : : {
835 : 0 : wImgSize = 1;
836 : 0 : }
837 : 0 : scaledSize.setHeight( scaledSize.width() * hwRatio );
838 : 0 : int hImgSize = static_cast< int >( scaledSize.height() );
839 : 0 : if ( hImgSize < 1 )
840 : : {
841 : 0 : hImgSize = 1;
842 : 0 : }
843 : 0 : return QSize( wImgSize, hImgSize );
844 : 0 : }
845 : :
846 : 0 : QImage QgsSvgCache::imageFromCachedPicture( const QgsSvgCacheEntry &entry ) const
847 : : {
848 : 0 : QSizeF viewBoxSize;
849 : 0 : QSizeF scaledSize;
850 : 0 : QImage image( sizeForImage( entry, viewBoxSize, scaledSize ), QImage::Format_ARGB32_Premultiplied );
851 : 0 : image.fill( 0 ); // transparent background
852 : :
853 : 0 : QPainter p( &image );
854 : 0 : p.drawPicture( QPoint( 0, 0 ), *entry.picture );
855 : 0 : return image;
856 : 0 : }
857 : :
|