OpenCPN Partial API docs
FontMgr.cpp
1 /***************************************************************************
2  *
3  * Project: OpenCPN
4  *
5  ***************************************************************************
6  * Copyright (C) 2013 by David S. Register *
7  * *
8  * This program is free software; you can redistribute it and/or modify *
9  * it under the terms of the GNU General Public License as published by *
10  * the Free Software Foundation; either version 2 of the License, or *
11  * (at your option) any later version. *
12  * *
13  * This program is distributed in the hope that it will be useful, *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16  * GNU General Public License for more details. *
17  * *
18  * You should have received a copy of the GNU General Public License *
19  * along with this program; if not, write to the *
20  * Free Software Foundation, Inc., *
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
22  **************************************************************************/
23 
24 #include <locale>
25 
26 #include <wx/gdicmn.h>
27 #include <wx/tokenzr.h>
28 
29 #include "FontMgr.h"
30 #include "OCPNPlatform.h"
31 #include "ocpn_plugin.h"
32 
34  wxFont *font;
35  int pointsize_req;
36  wxFontStyle style_req;
37  wxFontWeight weight_req;
38  bool underline_req;
39 };
40 
42 public:
43  ~OCPNwxFontList() { FreeAll(); }
44  wxFont *FindOrCreateFont(int pointSize, wxFontFamily family,
45  wxFontStyle style, wxFontWeight weight,
46  bool underline = false,
47  const wxString &face = wxEmptyString,
48  wxFontEncoding encoding = wxFONTENCODING_DEFAULT);
49  void FreeAll(void);
50 
51 private:
52  bool isCached(font_cache_record& record, int pointSize, wxFontFamily family,
53  wxFontStyle style, wxFontWeight weight,
54  bool underline, const wxString &facename,
55  wxFontEncoding encoding);
56 
57  std::vector<font_cache_record> m_fontVector;
58 };
59 
60 extern wxString g_locale;
61 
62 wxString s_locale;
63 int g_default_font_size;
64 wxString g_default_font_facename;
65 
66 FontMgr *FontMgr::instance = NULL;
67 
68 FontMgr &FontMgr::Get() {
69  if (!instance) instance = new FontMgr;
70  return *instance;
71 }
72 
73 void FontMgr::Shutdown() {
74  if (instance) {
75  delete instance;
76  instance = NULL;
77  }
78 }
79 
80 FontMgr::FontMgr() : m_wxFontCache(NULL), m_fontlist(NULL), pDefFont(NULL) {
81  // Create the list of fonts
82  m_fontlist = new FontList;
83  m_fontlist->DeleteContents(true);
84 
85  s_locale = g_locale;
86 
87  // Get a nice generic font as default
88  pDefFont = FindOrCreateFont(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
89  wxFONTWEIGHT_BOLD, FALSE, wxString(_T ( "" )),
90  wxFONTENCODING_SYSTEM);
91 }
92 
93 FontMgr::~FontMgr() {
94  m_fontlist->Clear();
95  delete m_fontlist;
96 
97  delete m_wxFontCache;
98 }
99 
100 void FontMgr::SetLocale(wxString &newLocale) { s_locale = newLocale; }
101 
102 wxColour FontMgr::GetFontColor(const wxString &TextElement) const {
103 
104  // Look thru the font list for a match
105  MyFontDesc *pmfd;
106  auto node = m_fontlist->GetFirst();
107  while (node) {
108  pmfd = node->GetData();
109  if (pmfd->m_dialogstring == TextElement) {
110  if (pmfd->m_configstring.BeforeFirst('-') == s_locale)
111  return pmfd->m_color;
112  }
113  node = node->GetNext();
114  }
115 
116  return wxColour(0, 0, 0);
117 }
118 
119 bool FontMgr::SetFontColor(const wxString &TextElement,
120  const wxColour color) const {
121  // Look thru the font list for a match
122  MyFontDesc *pmfd;
123  auto node = m_fontlist->GetFirst();
124  while (node) {
125  pmfd = node->GetData();
126  if (pmfd->m_dialogstring == TextElement) {
127  if (pmfd->m_configstring.BeforeFirst('-') == s_locale) {
128  pmfd->m_color = color;
129  return true;
130  }
131  }
132  node = node->GetNext();
133  }
134 
135  return false;
136 }
137 
138 wxString FontMgr::GetFontConfigKey(const wxString &description) {
139  // Create the configstring by combining the locale with
140  // a hash of the font description. Hash is used because the i18n
141  // description can contain characters that mess up the config file.
142 
143  wxString configkey;
144  configkey = s_locale;
145  configkey.Append(_T("-"));
146 
147  using namespace std;
148  locale loc;
149  const collate<char> &coll = use_facet<collate<char> >(loc);
150  // char cFontDesc[101];
151  // wcstombs( cFontDesc, description.c_str(), 100 );
152  // cFontDesc[100] = 0;
153 
154  wxCharBuffer abuf = description.ToUTF8();
155 
156  int fdLen = strlen(abuf);
157 
158  configkey.Append(wxString::Format(
159  _T("%08lx"), coll.hash(abuf.data(), abuf.data() + fdLen)));
160  return configkey;
161 }
162 
163 wxFont *FontMgr::GetFont(const wxString &TextElement, int user_default_size) {
164  // Look thru the font list for a match
165  MyFontDesc *pmfd;
166  auto node = m_fontlist->GetFirst();
167  while (node) {
168  pmfd = node->GetData();
169  if (pmfd->m_dialogstring == TextElement) {
170  if (pmfd->m_configstring.BeforeFirst('-') == s_locale)
171  return pmfd->m_font;
172  }
173  node = node->GetNext();
174  }
175 
176  // Found no font, so create a nice one and add to the list
177  wxString configkey = GetFontConfigKey(TextElement);
178 
179  // Now create a benign, always present native font
180  // with optional user requested default size
181 
182  // Get the system default font.
183  wxFont sys_font = *wxNORMAL_FONT;
184  int sys_font_size = sys_font.GetPointSize();
185  wxString FaceName = sys_font.GetFaceName();
186 
187  int new_size;
188  if (0 == user_default_size) {
189  if (g_default_font_size)
190  new_size = g_default_font_size;
191  else
192  new_size = sys_font_size;
193  } else
194  new_size = user_default_size;
195 
196  if (g_default_font_facename.Length()) FaceName = g_default_font_facename;
197 
198  wxString nativefont = GetSimpleNativeFont(new_size, FaceName);
199  wxFont *nf = wxFont::New(nativefont);
200 
201  wxColor color = GetDefaultFontColor( TextElement);
202 
203  MyFontDesc *pnewfd = new MyFontDesc(TextElement, configkey, nf, color);
204  m_fontlist->Append(pnewfd);
205 
206  return pnewfd->m_font;
207 }
208 
209 wxColour FontMgr::GetDefaultFontColor( const wxString &TextElement ){
210  wxColor defaultColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
211 
212  // Special cases here
213  if(TextElement.IsSameAs( _("Console Legend")) )
214  defaultColor = wxColour( 0, 255, 0);
215  else if(TextElement.IsSameAs( _("Console Value")) )
216  defaultColor = wxColour( 0, 255, 0);
217  else if(TextElement.IsSameAs( _("Marks")) )
218  defaultColor = wxColour( 0, 0, 0);
219  else if(TextElement.IsSameAs( _("RouteLegInfoRollover")) )
220  defaultColor = wxColour( 0, 0, 0);
221  else if(TextElement.IsSameAs( _("AISRollover")) )
222  defaultColor = wxColour( 0, 0, 0);
223  else if(TextElement.IsSameAs( _("ExtendedTideIcon")) )
224  defaultColor = wxColour( 0, 0, 0);
225  else if(TextElement.IsSameAs( _("ChartTexts")) )
226  defaultColor = wxColour( 0, 0, 0);
227  else if(TextElement.IsSameAs( _("AIS Target Name")) )
228  defaultColor = wxColour( 0, 0, 0);
229 #ifdef __WXMAC__
230  // Override, to adjust for light/dark mode
231  return wxColour(0,0,0);
232 #endif
233 
234  return defaultColor;
235 }
236 
237 wxString FontMgr::GetSimpleNativeFont(int size, wxString face) {
238  // Now create a benign, always present native string
239  wxString nativefont;
240 
241  // this should work for all platforms
242  nativefont = wxFont(size, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
243  wxFONTWEIGHT_NORMAL, false, face)
244  .GetNativeFontInfoDesc();
245 
246  return nativefont;
247 }
248 
249 bool FontMgr::SetFont(const wxString &TextElement, wxFont *pFont,
250  wxColour color) {
251  // Look thru the font list for a match
252  MyFontDesc *pmfd;
253  auto node = m_fontlist->GetFirst();
254  while (node) {
255  pmfd = node->GetData();
256  if (pmfd->m_dialogstring == TextElement) {
257  if (pmfd->m_configstring.BeforeFirst('-') == s_locale) {
258  // Todo Think about this
259  //
260 
261  // Cannot delete the present font, since it may be in use elsewhere
262  // This WILL leak....but only on font changes
263 
264  // delete pmfd->m_font; // purge
265  // any old value
266 
267  pmfd->m_font = pFont;
268  pmfd->m_nativeInfo = pFont->GetNativeFontInfoDesc();
269  pmfd->m_color = color;
270 
271  return true;
272  }
273  }
274  node = node->GetNext();
275  }
276 
277  return false;
278 }
279 
280 int FontMgr::GetNumFonts(void) const { return m_fontlist->GetCount(); }
281 
282 const wxString &FontMgr::GetConfigString(int i) const {
283  MyFontDesc *pfd = m_fontlist->Item(i)->GetData();
284  return pfd->m_configstring;
285 }
286 
287 const wxString &FontMgr::GetDialogString(int i) const {
288  MyFontDesc *pfd = m_fontlist->Item(i)->GetData();
289  return pfd->m_dialogstring;
290 }
291 
292 const wxString &FontMgr::GetNativeDesc(int i) const {
293  MyFontDesc *pfd = m_fontlist->Item(i)->GetData();
294  return pfd->m_nativeInfo;
295 }
296 
297 wxString FontMgr::GetFullConfigDesc(int i) const {
298  MyFontDesc *pfd = m_fontlist->Item(i)->GetData();
299  wxString ret = pfd->m_dialogstring;
300  ret.Append(_T ( ":" ));
301  ret.Append(pfd->m_nativeInfo);
302  ret.Append(_T ( ":" ));
303 
304  wxString cols(_T("rgb(0,0,0)"));
305  if (pfd->m_color.IsOk()) cols = pfd->m_color.GetAsString(wxC2S_CSS_SYNTAX);
306 
307  ret.Append(cols);
308  return ret;
309 }
310 
311 MyFontDesc *FontMgr::FindFontByConfigString(wxString pConfigString) {
312  // Search for a match in the list
313  MyFontDesc *pmfd;
314  auto node = m_fontlist->GetFirst();
315 
316  while (node) {
317  pmfd = node->GetData();
318  if (pmfd->m_configstring == pConfigString) {
319  return pmfd;
320  }
321  node = node->GetNext();
322  }
323 
324  return NULL;
325 }
326 
327 void FontMgr::LoadFontNative(wxString *pConfigString, wxString *pNativeDesc) {
328  // Parse the descriptor string
329 
330  wxStringTokenizer tk(*pNativeDesc, _T ( ":" ));
331  wxString dialogstring = tk.GetNextToken();
332  wxString nativefont = tk.GetNextToken();
333 
334  wxString c = tk.GetNextToken();
335  wxColour color(c); // from string description
336 
337  // Search for a match in the list
338  MyFontDesc *pmfd;
339  auto node = m_fontlist->GetFirst();
340 
341  while (node) {
342  pmfd = node->GetData();
343  if (pmfd->m_configstring == *pConfigString) {
344  if (pmfd->m_configstring.BeforeFirst('-') == g_locale) {
345  pmfd->m_nativeInfo = nativefont;
346  wxFont *nf = pmfd->m_font->New(pmfd->m_nativeInfo);
347  pmfd->m_font = nf;
348  break;
349  }
350  }
351  node = node->GetNext();
352  }
353 
354  // Create and add the font to the list
355  if (!node) {
356  wxFont *nf0 = new wxFont();
357 
358 #ifdef __OCPN__ANDROID__
359  wxFont *nf = new wxFont(nativefont);
360 #else
361  wxFont *nf = nf0->New(nativefont);
362 #endif
363 
364  double font_size = nf->GetPointSize();
365  wxString s = nf->GetNativeFontInfoDesc();
366 
367  // Scrub the native font string for bad unicode conversion
368 #ifdef __WXMSW__
369  wxString face = nf->GetFaceName();
370  const wxChar *t = face.c_str();
371  if (*t > 255) {
372  delete nf;
373  wxString substitute_native = GetSimpleNativeFont(12, _T(""));
374  nf = nf0->New(substitute_native);
375  }
376 #endif
377  delete nf0;
378 
379  MyFontDesc *pnewfd =
380  new MyFontDesc(dialogstring, *pConfigString, nf, color);
381  m_fontlist->Append(pnewfd);
382  }
383 }
384 
385 wxFont *FontMgr::FindOrCreateFont(int point_size, wxFontFamily family,
386  wxFontStyle style, wxFontWeight weight,
387  bool underline, const wxString &facename,
388  wxFontEncoding encoding) {
389  if (m_wxFontCache == 0) m_wxFontCache = new OCPNwxFontList;
390  return m_wxFontCache->FindOrCreateFont(point_size, family, style, weight,
391  underline, facename, encoding);
392 }
393 
394 bool OCPNwxFontList::isCached(font_cache_record& record, int pointSize, wxFontFamily family,
395  wxFontStyle style, wxFontWeight weight,
396  bool underline, const wxString &facename,
397  wxFontEncoding encoding) {
398  if (record.pointsize_req == pointSize && record.style_req == style &&
399  record.weight_req == weight && record.underline_req == underline) {
400  bool same;
401 
402  wxFont *font = record.font;
403  // empty facename matches anything at all: this is bad because
404  // depending on which fonts are already created, we might get back
405  // a different font if we create it with empty facename, but it is
406  // still better than never matching anything in the cache at all
407  // in this case
408  if (!facename.empty()) {
409  const wxString &fontFace = font->GetFaceName();
410 
411  // empty facename matches everything
412  same = !fontFace || fontFace == facename;
413  } else {
414  same = font->GetFamily() == family;
415  }
416  if (same && (encoding != wxFONTENCODING_DEFAULT)) {
417  // have to match the encoding too
418  same = font->GetEncoding() == encoding;
419  }
420  return same;
421  }
422  return false;
423 }
424 
425 wxFont *OCPNwxFontList::FindOrCreateFont(int pointSize, wxFontFamily family,
426  wxFontStyle style, wxFontWeight weight,
427  bool underline,
428  const wxString &facename,
429  wxFontEncoding encoding) {
430  // from wx source code
431  // In all ports but wxOSX, the effective family of a font created using
432  // wxFONTFAMILY_DEFAULT is wxFONTFAMILY_SWISS so this is what we need to
433  // use for comparison.
434  //
435  // In wxOSX the original wxFONTFAMILY_DEFAULT seems to be kept and it uses
436  // a different font than wxFONTFAMILY_SWISS anyhow so we just preserve it.
437 #ifndef __WXOSX__
438  if (family == wxFONTFAMILY_DEFAULT) family = wxFONTFAMILY_SWISS;
439 #endif // !__WXOSX__
440 
441  wxFont *font;
442  for (size_t i=0; i < m_fontVector.size() ; i++){
443  font_cache_record record = m_fontVector[i];
444  if (isCached(record, pointSize, family, style, weight, underline, facename,
445  encoding))
446  return record.font;
447  }
448 
449  // font not found, create the new one
450  // Support scaled HDPI displays automatically
451 
452  font = NULL;
453  wxFont fontTmp(OCPN_GetDisplayContentScaleFactor() * pointSize,
454  family, style, weight, underline, facename, encoding);
455  if (fontTmp.IsOk()) {
456  font = new wxFont(fontTmp);
457  font_cache_record record;
458  record.font = font;
459  record.pointsize_req = pointSize;
460  record.style_req = style;
461  record.weight_req = weight;
462  record.underline_req = underline;
463  m_fontVector.push_back(record);
464  }
465 
466  return font;
467 }
468 
469 void OCPNwxFontList::FreeAll(void) {
470  wxFont *font;
471  for (size_t i=0; i < m_fontVector.size() ; i++){
472  font_cache_record record = m_fontVector[i];
473  font = record.font;
474  delete font;
475  }
476  m_fontVector.clear();
477 }
478 
479 static wxString FontCandidates[] = {_T("AISTargetAlert"),
480  _T("AISTargetQuery"),
481  _T("StatusBar"),
482  _T("AIS Target Name" ),
483  _T("ObjectQuery"),
484  _T("RouteLegInfoRollover"),
485  _T("ExtendedTideIcon"),
486  _T("CurrentValue"),
487  _T("Console Legend"),
488  _T("Console Value"),
489  _T("AISRollover"),
490  _T("TideCurrentGraphRollover"),
491  _T("Marks"),
492  _T("ChartTexts"),
493  _T("ToolTips"),
494  _T("Dialog"),
495  _T("Menu"),
496  _T("GridText"),
497  _T("END_OF_LIST")};
498 
499 void FontMgr::ScrubList() {
500  wxString now_locale = g_locale;
501  wxArrayString string_array;
502 
503  // Build the composite candidate array
504  wxArrayString candidateArray;
505  unsigned int i = 0;
506 
507  // The fixed, static list
508  while (true) {
509  wxString candidate = FontCandidates[i];
510  if (candidate == _T("END_OF_LIST")) {
511  break;
512  }
513 
514  candidateArray.Add(candidate);
515  i++;
516  }
517 
518  // The Aux Key array
519  for (unsigned int i = 0; i < m_AuxKeyArray.GetCount(); i++) {
520  candidateArray.Add(m_AuxKeyArray[i]);
521  }
522 
523  for (unsigned int i = 0; i < candidateArray.GetCount(); i++) {
524  wxString candidate = candidateArray[i];
525 
526  // For each font identifier string in the FontCandidate array...
527 
528  // In the current locale, walk the loaded list looking for a translation
529  // that is correct, according to the currently load .mo file.
530  // If found, add to a temporary array
531 
532  wxString trans = wxGetTranslation(candidate);
533 
534  MyFontDesc *pmfd;
535  auto node = m_fontlist->GetFirst();
536  while (node) {
537  pmfd = node->GetData();
538  wxString tlocale = pmfd->m_configstring.BeforeFirst('-');
539  if (tlocale == now_locale) {
540  if (trans == pmfd->m_dialogstring) {
541  string_array.Add(pmfd->m_dialogstring);
542  }
543  }
544 
545  node = node->GetNext();
546  }
547  }
548 
549  // now we have an array of correct translations
550  // Walk the loaded list again.
551  // If a list item's translation is not in the "good" array, mark it for
552  // removal
553 
554  MyFontDesc *pmfd;
555  auto node = m_fontlist->GetFirst();
556  while (node) {
557  pmfd = node->GetData();
558  wxString tlocale = pmfd->m_configstring.BeforeFirst('-');
559  if (tlocale == now_locale) {
560  bool bfound = false;
561  for (unsigned int i = 0; i < string_array.GetCount(); i++) {
562  if (string_array[i] == pmfd->m_dialogstring) {
563  bfound = true;
564  break;
565  }
566  }
567  if (!bfound) { // mark for removal
568  pmfd->m_dialogstring = _T("");
569  pmfd->m_configstring = _T("");
570  }
571  }
572 
573  node = node->GetNext();
574  }
575 
576  // Remove the marked list items
577  node = m_fontlist->GetFirst();
578  while (node) {
579  pmfd = node->GetData();
580  if (pmfd->m_dialogstring == _T("")) {
581  bool bd = m_fontlist->DeleteObject(pmfd);
582  if (bd) node = m_fontlist->GetFirst();
583  } else
584  node = node->GetNext();
585  }
586 
587  // And finally, for good measure, make sure that everything in the candidate
588  // array has a valid entry in the list
589  i = 0;
590  while (true) {
591  wxString candidate = FontCandidates[i];
592  if (candidate == _T("END_OF_LIST")) {
593  break;
594  }
595 
596  GetFont(wxGetTranslation(candidate), g_default_font_size);
597 
598  i++;
599  }
600 }
601 
602 bool FontMgr::AddAuxKey(wxString key) {
603  for (unsigned int i = 0; i < m_AuxKeyArray.GetCount(); i++) {
604  if (m_AuxKeyArray[i] == key) return false;
605  }
606  m_AuxKeyArray.Add(key);
607  return true;
608 }
Manages the font list.
Definition: FontMgr.h:41