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 "base_platform.h"
48
49wxBitmap LoadSVG(const wxString filename, const unsigned int width,
50 const unsigned int height, wxBitmap* default_bitmap,
51 bool use_cache) {
52#ifdef ocpnUSE_SVG
53#ifndef ocpnUSE_wxBitmapBundle
54#ifdef __OCPN__ANDROID__
55 return loadAndroidSVG(filename, width, height);
56#else
57 wxSVGDocument svgDoc;
58 if (svgDoc.Load(filename))
59 return wxBitmap(svgDoc.Render(width, height, NULL, true, true));
60 else
61 return wxBitmap(width, height);
62#endif
63#else
64#ifdef __OCPN__ANDROID__
65 return loadAndroidSVG(filename, width, height);
66#else
67 wxSize s(width, height);
68 if (wxFileExists(filename)) {
69 wxBitmap bmp;
70 std::string key;
71 if (use_cache && SVGBitmapCache::GetInstance().HasKey(
72 key = SVGBitmapCache::GetInstance().MakeKey(
73 filename, width, height))) {
74 bmp = SVGBitmapCache::GetInstance().Get(key);
75 } else {
76 bmp = wxBitmapBundle::FromSVGFile(filename, s).GetBitmap(s);
77 if (use_cache) {
78 SVGBitmapCache::GetInstance().Add(key, bmp);
79 }
80 }
81 if (bmp.IsOk()) {
82 return bmp;
83 }
84 }
85 if (default_bitmap) {
86 return *default_bitmap;
87 } else {
88 return wxNullBitmap; // Or wxBitmap(width, height);?
89 }
90#endif
91#endif
92#else
93 return wxBitmap(width, height);
94#endif // ocpnUSE_SVG
95}
96
97/* returns 1 if str ends with suffix */
98int str_ends_with(const char* str, const char* suffix) {
99 if (str == NULL || suffix == NULL) return 0;
100
101 size_t str_len = strlen(str);
102 size_t suffix_len = strlen(suffix);
103
104 if (suffix_len > str_len) return 0;
105
106 return 0 == strncmp(str + str_len - suffix_len, suffix, suffix_len);
107}
108
109// Convert the provided value to the standard 96 DPI pixels
110// if such a conversion is doable. If not doable or not necessary (px, em, ex,
111// %), numerical part of the value is returned In case of an error, 0 is
112// returned
113unsigned int get_px_length(const char* val) {
114 int num;
115 try {
116 num = std::stoi(val);
117 } catch (std::invalid_argument const& ex) {
118 return 0;
119 } catch (std::out_of_range const& ex) {
120 return 0;
121 }
122 if (num < 0) {
123 return 0;
124 }
125
126 if (str_ends_with(val, "mm")) {
127 return (unsigned int)((float)num * SVG_MM_TO_PX);
128 } else if (str_ends_with(val, "cm")) {
129 return (unsigned int)((float)num * SVG_CM_TO_PX);
130 } else if (str_ends_with(val, "in")) {
131 return (unsigned int)((float)num * SVG_CM_TO_PX);
132 } else if (str_ends_with(val, "pt")) {
133 return (unsigned int)((float)num * SVG_PT_TO_PX);
134 }
135 return num;
136}
137
138bool SVGDocumentPixelSize(const wxString filename, unsigned int& width,
139 unsigned int& height) {
140 pugi::xml_document svgDoc;
141 if (svgDoc.load_file(filename.fn_str())) {
142 pugi::xml_node svgNode = svgDoc.child("svg");
143 for (pugi::xml_attribute attr = svgNode.first_attribute(); attr;
144 attr = attr.next_attribute()) {
145 const char* pca = attr.name();
146 if (!strcmp(pca, "width")) {
147 width = get_px_length(attr.as_string());
148 } else if (!strcmp(pca, "height")) {
149 height = get_px_length(attr.as_string());
150 }
151 }
152 }
153 return false;
154}
155
156extern BasePlatform* g_BasePlatform;
157extern float g_ChartScaleFactorExp;
158
159unsigned int SVGPixelsToDisplay(unsigned int svg_px) {
160 return g_BasePlatform->GetDisplayDPmm() * SVG_MM_TO_IN / SVG_IN_TO_PX * svg_px *
161 g_ChartScaleFactorExp;
162}
163
164SVGBitmapCache::SVGBitmapCache() {
165 wxFileName iconcachedir;
166 iconcachedir.SetName("iconCache");
167 iconcachedir.SetPath(g_BasePlatform->GetPrivateDataDir());
168 // Create the cache dir here if necessary
169 if (!wxDir::Exists(iconcachedir.GetFullPath())) {
170 wxFileName::Mkdir(iconcachedir.GetFullPath());
171 }
172 cache_directory = iconcachedir.GetFullPath();
173}
174
175std::string SVGBitmapCache::MakeKey(wxString file_path, const int width,
176 const int height) {
177 std::replace(file_path.begin(), file_path.end(), ':', '_');
178 std::replace(file_path.begin(), file_path.end(), '/', '_');
179 std::replace(file_path.begin(), file_path.end(), '\\', '_');
180 std::replace(file_path.begin(), file_path.end(), '>', '_');
181 std::replace(file_path.begin(), file_path.end(), '<', '_');
182 std::replace(file_path.begin(), file_path.end(), '"', '_');
183 std::replace(file_path.begin(), file_path.end(), '|', '_');
184 std::replace(file_path.begin(), file_path.end(), '?', '_');
185 std::replace(file_path.begin(), file_path.end(), '*', '_');
186
187 std::ostringstream ss;
188 ss << file_path << "_" << width << "x" << height;
189 return ss.str();
190}
191
192void SVGBitmapCache::Add(const wxString key, const wxBitmap bmp) {
193 if (!bmp.IsOk()) {
194 return;
195 }
196 sync.lock();
197 items.emplace(key, bmp);
198 wxFileName fn;
199 fn.SetName(key);
200 fn.SetPath(cache_directory);
201 bmp.SaveFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
202 sync.unlock();
203}
204
205wxBitmap SVGBitmapCache::Get(const wxString key) {
206 wxBitmap bmp = wxNullBitmap;
207 sync.lock();
208 std::unordered_map<std::string, wxBitmap>::const_iterator i =
209 items.find(key.ToStdString());
210 if (i != items.end()) {
211 bmp = i->second;
212 } else {
213 wxFileName fn;
214 fn.SetName(key);
215 fn.SetPath(cache_directory);
216 if (fn.FileExists()) {
217 bmp.LoadFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
218 if (bmp.IsOk()) {
219 items.emplace(key, bmp);
220 } else {
221 bmp = wxNullBitmap;
222 }
223 }
224 }
225 sync.unlock();
226 return bmp;
227}
228
229bool SVGBitmapCache::HasKey(const wxString key) {
230 bool res = false;
231 sync.lock();
232 if (items.find(key.ToStdString()) != items.end()) {
233 res = true;
234 } else {
235 wxFileName fn;
236 fn.SetName(key);
237 fn.SetPath(cache_directory);
238 if (fn.FileExists()) {
239 // We proactively also load it here if it exists
240 wxBitmap bmp;
241 bmp.LoadFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
242 if (bmp.IsOk()) {
243 items.emplace(key, bmp);
244 res = true;
245 }
246 }
247 }
248 sync.unlock();
249 return res;
250}