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 "model/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
50#define SVG_IN_TO_PT 72
51#define SVG_IN_TO_PX 96
52#define SVG_PT_TO_IN 1 / 72
53#define SVG_PX_TO_IN 1 / 96
54#define SVG_PT_TO_PX 96 / 72
55#define SVG_MM_TO_PX 3.7795275591
56#define SVG_PX_TO_MM 0.2645833333
57#define SVG_MM_TO_PT 2.8346456693
58#define SVG_PT_TO_MM 0.3527777778
59#define SVG_CM_TO_PX 37.795275591
60#define SVG_CM_TO_PT 28.346456693
61#define SVG_MM_TO_IN 25.4
62
63extern BasePlatform* g_BasePlatform;
64
65wxBitmap LoadSVG(const wxString filename, const unsigned int width,
66 const unsigned int height, wxBitmap* default_bitmap,
67 bool use_cache) {
68#ifdef ocpnUSE_SVG
69#ifndef ocpnUSE_wxBitmapBundle
70#ifdef __ANDROID__
71 return loadAndroidSVG(filename, width, height);
72#else
73 wxSVGDocument svgDoc;
74 if (svgDoc.Load(filename))
75 return wxBitmap(svgDoc.Render(width, height, NULL, true, true));
76 else
77 return wxBitmap(width, height);
78#endif
79#else
80#ifdef __ANDROID__
81 return loadAndroidSVG(filename, width, height);
82#else
83 wxSize s(width, height);
84 if (wxFileExists(filename)) {
85 wxBitmap bmp;
86 std::string key;
87 if (use_cache && SVGBitmapCache::GetInstance().HasKey(
88 key = SVGBitmapCache::GetInstance().MakeKey(
89 filename, width, height))) {
90 bmp = SVGBitmapCache::GetInstance().Get(key);
91 } else {
92 bmp = wxBitmapBundle::FromSVGFile(filename, s).GetBitmap(s);
93 if (use_cache) {
94 SVGBitmapCache::GetInstance().Add(key, bmp);
95 }
96 }
97 if (bmp.IsOk()) {
98 return bmp;
99 }
100 }
101 if (default_bitmap) {
102 return *default_bitmap;
103 } else {
104 return wxNullBitmap; // Or wxBitmap(width, height);?
105 }
106#endif
107#endif
108#else
109 return wxBitmap(width, height);
110#endif // ocpnUSE_SVG
111}
112
113/* returns 1 if str ends with suffix */
114int str_ends_with(const char* str, const char* suffix) {
115 if (str == NULL || suffix == NULL) return 0;
116
117 size_t str_len = strlen(str);
118 size_t suffix_len = strlen(suffix);
119
120 if (suffix_len > str_len) return 0;
121
122 return 0 == strncmp(str + str_len - suffix_len, suffix, suffix_len);
123}
124
125// Convert the provided value to the standard 96 DPI pixels
126// if such a conversion is doable. If not doable or not necessary (px, em, ex,
127// %), numerical part of the value is returned In case of an error, 0 is
128// returned
129unsigned int get_px_length(const char* val) {
130 int num;
131 try {
132 num = std::stoi(val);
133 } catch (std::invalid_argument&) {
134 return 0;
135 } catch (std::out_of_range&) {
136 return 0;
137 }
138 if (num < 0) {
139 return 0;
140 }
141
142 if (str_ends_with(val, "mm")) {
143 return (unsigned int)((float)num * SVG_MM_TO_PX);
144 } else if (str_ends_with(val, "cm")) {
145 return (unsigned int)((float)num * SVG_CM_TO_PX);
146 } else if (str_ends_with(val, "in")) {
147 return (unsigned int)((float)num * SVG_CM_TO_PX);
148 } else if (str_ends_with(val, "pt")) {
149 return (unsigned int)((float)num * SVG_PT_TO_PX);
150 }
151 return num;
152}
153
154bool SVGDocumentPixelSize(const wxString filename, unsigned int& width,
155 unsigned int& height) {
156 width = 0;
157 height = 0;
158 float viewBoxWidth = 0, viewBoxHeight = 0;
159 pugi::xml_document svgDoc;
160 if (svgDoc.load_file(filename.fn_str())) {
161 pugi::xml_node svgNode = svgDoc.child("svg");
162
163 // Check for viewBox attribute.
164 if (const char* viewBox = svgNode.attribute("viewBox").value()) {
165 std::istringstream iss(viewBox);
166 float minX, minY;
167 iss >> minX >> minY >> viewBoxWidth >> viewBoxHeight;
168 }
169
170 // Check width/height attributes
171 for (pugi::xml_attribute attr = svgNode.first_attribute(); attr;
172 attr = attr.next_attribute()) {
173 const char* pca = attr.name();
174 if (!strcmp(pca, "width")) {
175 width = get_px_length(attr.as_string());
176 } else if (!strcmp(pca, "height")) {
177 height = get_px_length(attr.as_string());
178 }
179 }
180 // SVG sizing rules: https://svgwg.org/specs/integration/#svg-css-sizing
181 if (width == 0 && height == 0) {
182 if (viewBoxWidth > 0 && viewBoxHeight > 0) {
183 // Use viewBox dimensions
184 width = viewBoxWidth;
185 height = viewBoxHeight;
186 } else {
187 // Default sizes per spec
188 width = 300;
189 height = 150;
190 }
191 } else if (width == 0) {
192 if (viewBoxWidth > 0 && viewBoxHeight > 0) {
193 width = height * (viewBoxWidth / viewBoxHeight);
194 } else {
195 width = 300;
196 }
197 } else if (height == 0) {
198 if (viewBoxWidth > 0 && viewBoxHeight > 0) {
199 height = width * (viewBoxHeight / viewBoxWidth);
200 } else {
201 height = 150;
202 }
203 }
204 }
205 return false;
206}
207
208unsigned int SVGPixelsToDisplay(unsigned int svg_px) {
209 return g_BasePlatform->GetDisplayDPmm() * SVG_MM_TO_IN / SVG_IN_TO_PX *
210 svg_px * g_ChartScaleFactorExp;
211}
212
213wxBitmap LoadSvgStdIcon(const std::string& svg_file, const wxWindow* w,
214 bool touch) {
215 auto path = fs::path(g_BasePlatform->GetSharedDataDir().ToStdString()) /
216 "uidata" / "MUI_flat" / svg_file;
217 int size = g_BasePlatform->GetSvgStdIconSize(w, touch);
218 return LoadSVG(path.string(), size, size);
219}
220
221SVGBitmapCache::SVGBitmapCache() {
222 wxFileName iconcachedir;
223 iconcachedir.SetName("iconCacheSVG");
224 iconcachedir.SetPath(g_BasePlatform->GetPrivateDataDir());
225 // Create the cache dir here if necessary
226 if (!wxDir::Exists(iconcachedir.GetFullPath())) {
227 wxFileName::Mkdir(iconcachedir.GetFullPath());
228 }
229 cache_directory = iconcachedir.GetFullPath();
230}
231
232std::string SVGBitmapCache::MakeKey(wxString file_path, const int width,
233 const int height) {
234 std::replace(file_path.begin(), file_path.end(), ':', '_');
235 std::replace(file_path.begin(), file_path.end(), '/', '_');
236 std::replace(file_path.begin(), file_path.end(), '\\', '_');
237 std::replace(file_path.begin(), file_path.end(), '>', '_');
238 std::replace(file_path.begin(), file_path.end(), '<', '_');
239 std::replace(file_path.begin(), file_path.end(), '"', '_');
240 std::replace(file_path.begin(), file_path.end(), '|', '_');
241 std::replace(file_path.begin(), file_path.end(), '?', '_');
242 std::replace(file_path.begin(), file_path.end(), '*', '_');
243
244 std::ostringstream ss;
245 ss << file_path << "_" << width << "x" << height;
246 return ss.str();
247}
248
249void SVGBitmapCache::Add(const wxString key, const wxBitmap bmp) {
250 if (!bmp.IsOk()) {
251 return;
252 }
253 sync.lock();
254 items.emplace(key, bmp);
255 wxFileName fn;
256 fn.SetName(key);
257 fn.SetPath(cache_directory);
258 bmp.SaveFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
259 sync.unlock();
260}
261
262wxBitmap SVGBitmapCache::Get(const wxString key) {
263 wxBitmap bmp = wxNullBitmap;
264 sync.lock();
265 std::unordered_map<std::string, wxBitmap>::const_iterator i =
266 items.find(key.ToStdString());
267 if (i != items.end()) {
268 bmp = i->second;
269 } else {
270 wxFileName fn;
271 fn.SetName(key);
272 fn.SetPath(cache_directory);
273 if (fn.FileExists()) {
274 bmp.LoadFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
275 if (bmp.IsOk()) {
276 items.emplace(key, bmp);
277 } else {
278 bmp = wxNullBitmap;
279 }
280 }
281 }
282 sync.unlock();
283 return bmp;
284}
285
286bool SVGBitmapCache::HasKey(const wxString key) {
287 bool res = false;
288 sync.lock();
289 if (items.find(key.ToStdString()) != items.end()) {
290 res = true;
291 } else {
292 wxFileName fn;
293 fn.SetName(key);
294 fn.SetPath(cache_directory);
295 if (fn.FileExists()) {
296 // We proactively also load it here if it exists
297 wxBitmap bmp;
298 bmp.LoadFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
299 if (bmp.IsOk()) {
300 items.emplace(key, bmp);
301 res = true;
302 }
303 }
304 }
305 sync.unlock();
306 return res;
307}
wxString & GetPrivateDataDir()
Return dir path for opencpn.log, etc., respecting -c cli option.
int GetSvgStdIconSize(const wxWindow *w, bool touch) override
Return icon size roughly corresponding to height of a char in w, tweaked to be "big enough" for touch...
SVG utilities.