OpenCPN Partial API docs
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 
50 wxBitmap 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 */
99 int 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
114 unsigned 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 
139 bool SVGDocumentPixelSize(const wxString filename, unsigned int& width,
140  unsigned int& height) {
141  width = 0;
142  height = 0;
143  pugi::xml_document svgDoc;
144  if (svgDoc.load_file(filename.fn_str())) {
145  pugi::xml_node svgNode = svgDoc.child("svg");
146  for (pugi::xml_attribute attr = svgNode.first_attribute(); attr;
147  attr = attr.next_attribute()) {
148  const char* pca = attr.name();
149  if (!strcmp(pca, "width")) {
150  width = get_px_length(attr.as_string());
151  } else if (!strcmp(pca, "height")) {
152  height = get_px_length(attr.as_string());
153  }
154  }
155  }
156  return false;
157 }
158 
159 extern BasePlatform* g_BasePlatform;
160 
161 unsigned int SVGPixelsToDisplay(unsigned int svg_px) {
162  return g_BasePlatform->GetDisplayDPmm() * SVG_MM_TO_IN / SVG_IN_TO_PX * svg_px *
163  g_ChartScaleFactorExp;
164 }
165 
166 SVGBitmapCache::SVGBitmapCache() {
167  wxFileName iconcachedir;
168  iconcachedir.SetName("iconCacheSVG");
169  iconcachedir.SetPath(g_BasePlatform->GetPrivateDataDir());
170  // Create the cache dir here if necessary
171  if (!wxDir::Exists(iconcachedir.GetFullPath())) {
172  wxFileName::Mkdir(iconcachedir.GetFullPath());
173  }
174  cache_directory = iconcachedir.GetFullPath();
175 }
176 
177 std::string SVGBitmapCache::MakeKey(wxString file_path, const int width,
178  const int height) {
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  std::replace(file_path.begin(), file_path.end(), '?', '_');
187  std::replace(file_path.begin(), file_path.end(), '*', '_');
188 
189  std::ostringstream ss;
190  ss << file_path << "_" << width << "x" << height;
191  return ss.str();
192 }
193 
194 void SVGBitmapCache::Add(const wxString key, const wxBitmap bmp) {
195  if (!bmp.IsOk()) {
196  return;
197  }
198  sync.lock();
199  items.emplace(key, bmp);
200  wxFileName fn;
201  fn.SetName(key);
202  fn.SetPath(cache_directory);
203  bmp.SaveFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
204  sync.unlock();
205 }
206 
207 wxBitmap SVGBitmapCache::Get(const wxString key) {
208  wxBitmap bmp = wxNullBitmap;
209  sync.lock();
210  std::unordered_map<std::string, wxBitmap>::const_iterator i =
211  items.find(key.ToStdString());
212  if (i != items.end()) {
213  bmp = i->second;
214  } else {
215  wxFileName fn;
216  fn.SetName(key);
217  fn.SetPath(cache_directory);
218  if (fn.FileExists()) {
219  bmp.LoadFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
220  if (bmp.IsOk()) {
221  items.emplace(key, bmp);
222  } else {
223  bmp = wxNullBitmap;
224  }
225  }
226  }
227  sync.unlock();
228  return bmp;
229 }
230 
231 bool SVGBitmapCache::HasKey(const wxString key) {
232  bool res = false;
233  sync.lock();
234  if (items.find(key.ToStdString()) != items.end()) {
235  res = true;
236  } else {
237  wxFileName fn;
238  fn.SetName(key);
239  fn.SetPath(cache_directory);
240  if (fn.FileExists()) {
241  // We proactively also load it here if it exists
242  wxBitmap bmp;
243  bmp.LoadFile(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
244  if (bmp.IsOk()) {
245  items.emplace(key, bmp);
246  res = true;
247  }
248  }
249  }
250  sync.unlock();
251  return res;
252 }
wxString & GetPrivateDataDir()
Return dir path for opencpn.log, etc., respecting -c cli option.