OpenCPN Partial API docs
Loading...
Searching...
No Matches
svg_utils.cpp
1/******************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: SVG Utility functions
5 * Author: David Register
6 *
7 ***************************************************************************
8 * Copyright (C) 2018 by David S. Register *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 ***************************************************************************
25 *
26 *
27 */
28
29#include "svg_utils.h"
30
31#ifdef ocpnUSE_SVG
32#ifndef ocpnUSE_wxBitmapBundle
33#include "wxSVG/svg.h"
34#else
35#include <wx/bmpbndl.h>
36#endif
37#endif // ocpnUSE_SVG
38#include <wx/filename.h>
39#include <wx/dir.h>
40
41#ifdef __OCPN__ANDROID__
42#include "androidUTIL.h"
43#include "qdebug.h"
44#endif
45
46#include "pugixml.hpp"
47#include "model/base_platform.h"
48#include "model/routeman.h"
49
50wxBitmap LoadSVG(const wxString filename, const unsigned int width,
51 const unsigned int height, wxBitmap* default_bitmap,
52 bool use_cache) {
53#ifdef ocpnUSE_SVG
54#ifndef ocpnUSE_wxBitmapBundle
55#ifdef __OCPN__ANDROID__
56 return loadAndroidSVG(filename, width, height);
57#else
58 wxSVGDocument svgDoc;
59 if (svgDoc.Load(filename))
60 return wxBitmap(svgDoc.Render(width, height, NULL, true, true));
61 else
62 return wxBitmap(width, height);
63#endif
64#else
65#ifdef __OCPN__ANDROID__
66 return loadAndroidSVG(filename, width, height);
67#else
68 wxSize s(width, height);
69 if (wxFileExists(filename)) {
70 wxBitmap bmp;
71 std::string key;
72 if (use_cache && SVGBitmapCache::GetInstance().HasKey(
73 key = SVGBitmapCache::GetInstance().MakeKey(
74 filename, width, height))) {
75 bmp = SVGBitmapCache::GetInstance().Get(key);
76 } else {
77 bmp = wxBitmapBundle::FromSVGFile(filename, s).GetBitmap(s);
78 if (use_cache) {
79 SVGBitmapCache::GetInstance().Add(key, bmp);
80 }
81 }
82 if (bmp.IsOk()) {
83 return bmp;
84 }
85 }
86 if (default_bitmap) {
87 return *default_bitmap;
88 } else {
89 return wxNullBitmap; // Or wxBitmap(width, height);?
90 }
91#endif
92#endif
93#else
94 return wxBitmap(width, height);
95#endif // ocpnUSE_SVG
96}
97
98/* returns 1 if str ends with suffix */
99int str_ends_with(const char* str, const char* suffix) {
100 if (str == NULL || suffix == NULL) return 0;
101
102 size_t str_len = strlen(str);
103 size_t suffix_len = strlen(suffix);
104
105 if (suffix_len > str_len) return 0;
106
107 return 0 == strncmp(str + str_len - suffix_len, suffix, suffix_len);
108}
109
110// Convert the provided value to the standard 96 DPI pixels
111// if such a conversion is doable. If not doable or not necessary (px, em, ex,
112// %), numerical part of the value is returned In case of an error, 0 is
113// returned
114unsigned int get_px_length(const char* val) {
115 int num;
116 try {
117 num = std::stoi(val);
118 } catch (std::invalid_argument&) {
119 return 0;
120 } catch (std::out_of_range&) {
121 return 0;
122 }
123 if (num < 0) {
124 return 0;
125 }
126
127 if (str_ends_with(val, "mm")) {
128 return (unsigned int)((float)num * SVG_MM_TO_PX);
129 } else if (str_ends_with(val, "cm")) {
130 return (unsigned int)((float)num * SVG_CM_TO_PX);
131 } else if (str_ends_with(val, "in")) {
132 return (unsigned int)((float)num * SVG_CM_TO_PX);
133 } else if (str_ends_with(val, "pt")) {
134 return (unsigned int)((float)num * SVG_PT_TO_PX);
135 }
136 return num;
137}
138
139bool SVGDocumentPixelSize(const wxString filename, unsigned int& width,
140 unsigned int& height) {
141 width = 0;
142 height = 0;
143 float viewBoxWidth = 0, viewBoxHeight = 0;
144 pugi::xml_document svgDoc;
145 if (svgDoc.load_file(filename.fn_str())) {
146 pugi::xml_node svgNode = svgDoc.child("svg");
147
148 // Check for viewBox attribute.
149 if (const char* viewBox = svgNode.attribute("viewBox").value()) {
150 std::istringstream iss(viewBox);
151 float minX, minY;
152 iss >> minX >> minY >> viewBoxWidth >> viewBoxHeight;
153 }
154
155 // Check width/height attributes
156 for (pugi::xml_attribute attr = svgNode.first_attribute(); attr;
157 attr = attr.next_attribute()) {
158 const char* pca = attr.name();
159 if (!strcmp(pca, "width")) {
160 width = get_px_length(attr.as_string());
161 } else if (!strcmp(pca, "height")) {
162 height = get_px_length(attr.as_string());
163 }
164 }
165 // SVG sizing rules: https://svgwg.org/specs/integration/#svg-css-sizing
166 if (width == 0 && height == 0) {
167 if (viewBoxWidth > 0 && viewBoxHeight > 0) {
168 // Use viewBox dimensions
169 width = viewBoxWidth;
170 height = viewBoxHeight;
171 } else {
172 // Default sizes per spec
173 width = 300;
174 height = 150;
175 }
176 } else if (width == 0) {
177 if (viewBoxWidth > 0 && viewBoxHeight > 0) {
178 width = height * (viewBoxWidth / viewBoxHeight);
179 } else {
180 width = 300;
181 }
182 } else if (height == 0) {
183 if (viewBoxWidth > 0 && viewBoxHeight > 0) {
184 height = width * (viewBoxHeight / viewBoxWidth);
185 } else {
186 height = 150;
187 }
188 }
189 }
190 return false;
191}
192
193extern BasePlatform* g_BasePlatform;
194
195unsigned int SVGPixelsToDisplay(unsigned int svg_px) {
196 return g_BasePlatform->GetDisplayDPmm() * SVG_MM_TO_IN / SVG_IN_TO_PX *
197 svg_px * g_ChartScaleFactorExp;
198}
199
200SVGBitmapCache::SVGBitmapCache() {
201 wxFileName iconcachedir;
202 iconcachedir.SetName("iconCacheSVG");
203 iconcachedir.SetPath(g_BasePlatform->GetPrivateDataDir());
204 // Create the cache dir here if necessary
205 if (!wxDir::Exists(iconcachedir.GetFullPath())) {
206 wxFileName::Mkdir(iconcachedir.GetFullPath());
207 }
208 cache_directory = iconcachedir.GetFullPath();
209}
210
211std::string SVGBitmapCache::MakeKey(wxString file_path, const int width,
212 const int height) {
213 std::replace(file_path.begin(), file_path.end(), ':', '_');
214 std::replace(file_path.begin(), file_path.end(), '/', '_');
215 std::replace(file_path.begin(), file_path.end(), '\\', '_');
216 std::replace(file_path.begin(), file_path.end(), '>', '_');
217 std::replace(file_path.begin(), file_path.end(), '<', '_');
218 std::replace(file_path.begin(), file_path.end(), '"', '_');
219 std::replace(file_path.begin(), file_path.end(), '|', '_');
220 std::replace(file_path.begin(), file_path.end(), '?', '_');
221 std::replace(file_path.begin(), file_path.end(), '*', '_');
222
223 std::ostringstream ss;
224 ss << file_path << "_" << width << "x" << height;
225 return ss.str();
226}
227
228void SVGBitmapCache::Add(const wxString key, const wxBitmap bmp) {
229 if (!bmp.IsOk()) {
230 return;
231 }
232 sync.lock();
233 items.emplace(key, bmp);
234 wxFileName fn;
235 fn.SetName(key);
236 fn.SetPath(cache_directory);
237 bmp.SaveFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
238 sync.unlock();
239}
240
241wxBitmap SVGBitmapCache::Get(const wxString key) {
242 wxBitmap bmp = wxNullBitmap;
243 sync.lock();
244 std::unordered_map<std::string, wxBitmap>::const_iterator i =
245 items.find(key.ToStdString());
246 if (i != items.end()) {
247 bmp = i->second;
248 } else {
249 wxFileName fn;
250 fn.SetName(key);
251 fn.SetPath(cache_directory);
252 if (fn.FileExists()) {
253 bmp.LoadFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
254 if (bmp.IsOk()) {
255 items.emplace(key, bmp);
256 } else {
257 bmp = wxNullBitmap;
258 }
259 }
260 }
261 sync.unlock();
262 return bmp;
263}
264
265bool SVGBitmapCache::HasKey(const wxString key) {
266 bool res = false;
267 sync.lock();
268 if (items.find(key.ToStdString()) != items.end()) {
269 res = true;
270 } else {
271 wxFileName fn;
272 fn.SetName(key);
273 fn.SetPath(cache_directory);
274 if (fn.FileExists()) {
275 // We proactively also load it here if it exists
276 wxBitmap bmp;
277 bmp.LoadFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
278 if (bmp.IsOk()) {
279 items.emplace(key, bmp);
280 res = true;
281 }
282 }
283 }
284 sync.unlock();
285 return res;
286}
wxString & GetPrivateDataDir()
Return dir path for opencpn.log, etc., respecting -c cli option.