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 #ifndef QGSSVGCACHE_H
19 #define QGSSVGCACHE_H
20 
21 #include "qgsabstractcontentcache.h"
22 #include "qgis.h"
23 
24 #include <QPicture>
25 
26 class QDomElement;
27 
28 #ifndef SIP_RUN
29 
30 ///@cond PRIVATE
31 
32 /**
33  * \ingroup core
34  * \class QgsSvgCacheEntry
35  */
36 class CORE_EXPORT QgsSvgCacheEntry : public QgsAbstractContentCacheEntry
37 {
38   public:
39 
40     /**
41      * Constructor.
42      * \param path Absolute path to SVG file (relative paths are not resolved).
43      * \param size
44      * \param strokeWidth width of stroke
45      * \param widthScaleFactor width scale factor
46      * \param fill color of fill
47      * \param stroke color of stroke
48      * \param fixedAspectRatio fixed aspect ratio (optional)
49      * \param parameters an optional map of parameters to dynamically replace content in the SVG
50      */
51     QgsSvgCacheEntry( const QString &path, double size, double strokeWidth, double widthScaleFactor, const QColor &fill, const QColor &stroke,
52                       double fixedAspectRatio = 0, const QMap<QString, QString> &parameters = QMap<QString, QString>() ) ;
53 
54     //! QgsSvgCacheEntry cannot be copied.
55     QgsSvgCacheEntry( const QgsSvgCacheEntry &rh ) = delete;
56     //! QgsSvgCacheEntry cannot be copied.
57     QgsSvgCacheEntry &operator=( const QgsSvgCacheEntry &rh ) = delete;
58 
59     double size = 0.0; //size in pixels (cast to int for QImage)
60     double strokeWidth = 0;
61     double widthScaleFactor = 1.0;
62 
63     //! Fixed aspect ratio
64     double fixedAspectRatio = 0;
65 
66     /**
67      * SVG viewbox size.
68      * \since QGIS 2.14
69      */
70     QSizeF viewboxSize;
71 
72     QColor fill = Qt::black;
73     QColor stroke = Qt::black;
74     QMap<QString, QString> parameters;
75 
76     std::unique_ptr< QImage > image;
77     std::unique_ptr< QPicture > picture;
78     //content (with params replaced)
79     QByteArray svgContent;
80 
81     /**
82      * TRUE if the image represents a broken/missing path.
83      *
84      * \since QGIS 3.14
85      */
86     bool isMissingImage = false;
87 
88     bool isEqual( const QgsAbstractContentCacheEntry *other ) const override;
89     int dataSize() const override;
90     void dump() const override;
91 
92 };
93 
94 ///@endcond
95 #endif
96 
97 /**
98  * \ingroup core
99  * \brief A cache for images / pictures derived from SVG files
100  *
101  * This class supports parameter replacement in SVG files according to the SVG params specification
102  * (http://www.w3.org/TR/2009/WD-SVGParamPrimer-20090616/).
103  *
104  * Supported parameters are:
105  *
106  * - \a param(fill): fill color (with no opacity value)
107  * - \a param(fill-opacity): fill color opacity
108  * - \a param(outline): outline color (with no opacity value)
109  * - \a param(outline-opacity): outline color opacity
110  * - \a param(outline-width): width of outline strokes
111  *
112  * E.g:
113  *
114  *   <circle fill="param(fill-color red)" stroke="param(pen-color black)" stroke-width="param(outline-width 1)"
115  *
116  * \note QgsSvgCache is not usually directly created, but rather accessed through QgsApplication::svgCache().
117 */
118 #ifdef SIP_RUN
119 class CORE_EXPORT QgsSvgCache : public QgsAbstractContentCacheBase // for sip we skip to the base class and avoid the template difficulty
120 {
121 #else
122 class CORE_EXPORT QgsSvgCache : public QgsAbstractContentCache< QgsSvgCacheEntry >
123 {
124 #endif
125     Q_OBJECT
126 
127   public:
128 
129     /**
130      * Constructor for QgsSvgCache.
131      */
132     QgsSvgCache( QObject *parent SIP_TRANSFERTHIS = nullptr );
133 
134     /**
135      * Returns an SVG drawing as a QImage.
136      *
137      * \param path Absolute path to SVG file.
138      * \param size size of cached image
139      * \param fill color of fill
140      * \param stroke color of stroke
141      * \param strokeWidth width of stroke
142      * \param widthScaleFactor width scale factor
143      * \param fitsInCache
144      * \param fixedAspectRatio fixed aspect ratio (optional)
145      * \param blocking forces to wait for loading before returning image (optional).
146      * \param parameters is a map of parameters to dynamically replace content in SVG.
147      *
148      * \warning The \a blocking parameter must NEVER be TRUE from GUI based applications (like the main QGIS
149      * application) or crashes will result. Only for use in external scripts or QGIS server.
150      */
151     QImage svgAsImage( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
152                        double widthScaleFactor, bool &fitsInCache, double fixedAspectRatio = 0, bool blocking = false,
153                        const QMap<QString, QString> &parameters = QMap<QString, QString>() );
154 
155     /**
156      * Returns an SVG drawing as a QPicture.
157      *
158      * \param path Absolute path to SVG file.
159      * \param size size of cached image
160      * \param fill color of fill
161      * \param stroke color of stroke
162      * \param strokeWidth width of stroke
163      * \param widthScaleFactor width scale factor
164      * \param forceVectorOutput
165      * \param fixedAspectRatio fixed aspect ratio (optional)
166      * \param blocking forces to wait for loading before returning image (optional)
167      * \param parameters is a map of parameters to dynamically replace content in SVG.
168      *
169      * \note The returned QPicture contains the SVG file centered over the picture origin. I.e. if it is rendered
170      * using QPainter::drawPicture( QPointF( 5, 10 ), picture ) it will be drawn centered over the point (5, 10).
171      * Appropriate translation to the destination painter based on the picture's boundingRect may need to be applied
172      * if rendering the SVG using the top-left or other reference point is desired.
173      *
174      * \warning The \a blocking parameter must NEVER be TRUE from GUI based applications (like the main QGIS
175      * application) or crashes will result. Only for use in external scripts or QGIS server.
176      */
177     QPicture svgAsPicture( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
178                            double widthScaleFactor, bool forceVectorOutput = false, double fixedAspectRatio = 0, bool blocking = false,
179                            const QMap<QString, QString> &parameters = QMap<QString, QString>() );
180 
181     /**
182      * Calculates the viewbox size of a (possibly cached) SVG file.
183      * \param path Absolute path to SVG file.
184      * \param size size of cached image
185      * \param fill color of fill
186      * \param stroke color of stroke
187      * \param strokeWidth width of stroke
188      * \param widthScaleFactor width scale factor
189      * \param fixedAspectRatio fixed aspect ratio (optional)
190      * \param blocking forces to wait for loading before returning image (optional).
191      * \param parameters is a map of parameters to dynamically replace content in SVG.
192      * \returns viewbox size set in SVG file
193      *
194      * \warning The blocking parameter must NEVER be TRUE from GUI based applications (like the main QGIS
195      * application) or crashes will result. Only for use in external scripts or QGIS server.
196      *
197      * \since QGIS 2.14
198      */
199     QSizeF svgViewboxSize( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
200                            double widthScaleFactor, double fixedAspectRatio = 0, bool blocking = false, const QMap<QString, QString> &parameters = QMap<QString, QString>() );
201 
202     /**
203      * Tests if an SVG file contains parameters for fill, stroke color, stroke width. If yes, possible default values are returned. If there are several
204      * default values in the SVG file, only the first one is considered. Blocking forces to wait for loading before returning image (optional). WARNING: the
205      * blocking parameter must NEVER be TRUE from GUI based applications (like the main QGIS application) or crashes will result. Only for use in external
206      * scripts or QGIS server.
207     */
208     void containsParams( const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasStrokeParam, QColor &defaultStrokeColor, bool &hasStrokeWidthParam,
209                          double &defaultStrokeWidth, bool blocking = false ) const;
210 
211     /**
212      * Tests if an SVG file contains parameters for fill, stroke color, stroke width. If yes, possible default values are returned. If there are several
213      * default values in the SVG file, only the first one is considered.
214      * \param path path to SVG file
215      * \param hasFillParam will be TRUE if fill param present in SVG
216      * \param hasDefaultFillParam will be TRUE if fill param has a default value specified
217      * \param defaultFillColor will be set to default fill color specified in SVG, if present
218      * \param hasFillOpacityParam will be TRUE if fill opacity param present in SVG
219      * \param hasDefaultFillOpacity will be TRUE if fill opacity param has a default value specified
220      * \param defaultFillOpacity will be set to default fill opacity specified in SVG, if present
221      * \param hasStrokeParam will be TRUE if stroke param present in SVG
222      * \param hasDefaultStrokeColor will be TRUE if stroke param has a default value specified
223      * \param defaultStrokeColor will be set to default stroke color specified in SVG, if present
224      * \param hasStrokeWidthParam will be TRUE if stroke width param present in SVG
225      * \param hasDefaultStrokeWidth will be TRUE if stroke width param has a default value specified
226      * \param defaultStrokeWidth will be set to default stroke width specified in SVG, if present
227      * \param hasStrokeOpacityParam will be TRUE if stroke opacity param present in SVG
228      * \param hasDefaultStrokeOpacity will be TRUE if stroke opacity param has a default value specified
229      * \param defaultStrokeOpacity will be set to default stroke opacity specified in SVG, if present
230      * \param blocking forces to wait for loading before returning image (optional).
231      *
232      * \note Available in Python bindings as containsParamsV3
233      *
234      * \warning The \a blocking parameter must NEVER be TRUE from GUI based applications (like the main QGIS
235      * application) or crashes will result. Only for use in external scripts or QGIS server.
236      *
237      * \since QGIS 2.14
238      */
239     void containsParams( const QString &path, bool &hasFillParam, bool &hasDefaultFillParam, QColor &defaultFillColor,
240                          bool &hasFillOpacityParam, bool &hasDefaultFillOpacity, double &defaultFillOpacity,
241                          bool &hasStrokeParam, bool &hasDefaultStrokeColor, QColor &defaultStrokeColor,
242                          bool &hasStrokeWidthParam, bool &hasDefaultStrokeWidth, double &defaultStrokeWidth,
243                          bool &hasStrokeOpacityParam, bool &hasDefaultStrokeOpacity, double &defaultStrokeOpacity,
244                          bool blocking = false ) const SIP_PYNAME( containsParamsV3 );
245 
246     /**
247      * Gets the SVG content corresponding to the given \a path.
248      *
249      * \a path may be a local file, remote (HTTP) url, or a base 64 encoded string (with a "base64:" prefix).
250      *
251      * The class default missingContent byte array is returned if the \a path could not be resolved or is broken. If
252      * the \a path corresponds to a remote URL, then class default fetchingContent will be returned while the content
253      * is in the process of being fetched.
254      * The \a blocking boolean forces to wait for loading before returning result. The content is loaded
255      * in the same thread to ensure provided the remote content.
256      *
257      * \warning The \a blocking parameter must NEVER be TRUE from GUI based applications (like the main QGIS application)
258      * or crashes will result. Only for use in external scripts or QGIS server.
259      */
260     QByteArray getImageData( const QString &path, bool blocking = false ) const;
261 
262     /**
263      * Gets the SVG content corresponding to the given \a path.
264      *
265      * \a path may be a local file, remote (HTTP) url, or a base 64 encoded string (with a "base64:" prefix).
266      *
267      * The parameters \a size, \a strokeWidth for width of stroke, \a widthScaleFactor for width scale factor,
268      * \a fill for color of fill, \a stroke for color of stroke and \a fixedAspectRatio for fixed aspect ratio (optional)
269      * are needed to get the entry from cache or creates a new entry if it does not exist already.
270      *
271      * The \a blocking boolean forces to wait for loading before returning image. The content is loaded
272      * in the same thread to ensure provided the image.
273      *
274      * \warning The \a blocking parameter must NEVER be TRUE from GUI based applications (like the main QGIS application)
275      * or crashes will result. Only for use in external scripts or QGIS server.
276      */
277 #ifndef SIP_RUN
278     QByteArray svgContent( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
279                            double widthScaleFactor, double fixedAspectRatio = 0, bool blocking = false, const QMap<QString, QString> &parameters = QMap<QString, QString>(), bool *isMissingImage = nullptr );
280 #else
281     QByteArray svgContent( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
282                            double widthScaleFactor, double fixedAspectRatio = 0, bool blocking = false, const QMap<QString, QString> &parameters = QMap<QString, QString>() );
283 #endif
284 
285   signals:
286 
287     /**
288      * Emit a signal to be caught by qgisapp and display a msg on status bar.
289      * \deprecated Deprecated since QGIS 3.6 -- no longer emitted.
290      */
291     Q_DECL_DEPRECATED void statusChanged( const QString  &statusQString ) SIP_DEPRECATED;
292 
293     /**
294      * Emitted when the cache has finished retrieving an SVG file from a remote \a url.
295      * \since QGIS 3.2
296      */
297     void remoteSvgFetched( const QString &url );
298 
299   protected:
300 
301     bool checkReply( QNetworkReply *reply, const QString &path ) const override;
302 
303   private:
304 
305     void replaceParamsAndCacheSvg( QgsSvgCacheEntry *entry, bool blocking = false );
306     void cacheImage( QgsSvgCacheEntry *entry );
307     void cachePicture( QgsSvgCacheEntry *entry, bool forceVectorOutput = false );
308     //! Returns entry from cache or creates a new entry if it does not exist already
309     QgsSvgCacheEntry *cacheEntry( const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth,
310                                   double widthScaleFactor, double fixedAspectRatio = 0, const QMap<QString, QString> &parameters = QMap<QString, QString>(), bool blocking = false, bool *isMissingImage = nullptr );
311 
312     //! Replaces parameters in elements of a dom node and calls method for all child nodes
313     void replaceElemParams( QDomElement &elem, const QColor &fill, const QColor &stroke, double strokeWidth, const QMap<QString, QString> &parameters );
314 
315     void containsElemParams( const QDomElement &elem,
316                              bool &hasFillParam, bool &hasDefaultFill, QColor &defaultFill,
317                              bool &hasFillOpacityParam, bool &hasDefaultFillOpacity, double &defaultFillOpacity,
318                              bool &hasStrokeParam, bool &hasDefaultStroke, QColor &defaultStroke,
319                              bool &hasStrokeWidthParam, bool &hasDefaultStrokeWidth, double &defaultStrokeWidth,
320                              bool &hasStrokeOpacityParam, bool &hasDefaultStrokeOpacity, double &defaultStrokeOpacity ) const SIP_PYNAME( containsParamsV3 );
321 
322     //! Calculates scaling for rendered image sizes to SVG logical sizes
323     double calcSizeScaleFactor( QgsSvgCacheEntry *entry, const QDomElement &docElem, QSizeF &viewboxSize ) const;
324 
325     /**
326      * Returns the target size (in pixels) and calculates the \a viewBoxSize
327      * for a cache \a entry.
328      */
329     QSize sizeForImage( const QgsSvgCacheEntry &entry, QSizeF &viewBoxSize, QSizeF &scaledSize ) const;
330 
331     /**
332      * Returns a rendered image for a cached picture \a entry.
333      */
334     QImage imageFromCachedPicture( const QgsSvgCacheEntry &entry ) const;
335 
336     //! SVG content to be rendered if SVG file was not found.
337     QByteArray mMissingSvg;
338 
339     QByteArray mFetchingSvg;
340 
341     friend class TestQgsSvgCache;
342 };
343 
344 #endif // QGSSVGCACHE_H
345