Merge pull request #4687 from ruuk/textboxgettext
[vuplus_xbmc] / xbmc / guilib / GUIFontManager.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "GUIFontManager.h"
22 #include "GraphicContext.h"
23 #include "GUIWindowManager.h"
24 #include "addons/Skin.h"
25 #include "GUIFontTTF.h"
26 #include "GUIFont.h"
27 #include "utils/XMLUtils.h"
28 #include "GUIControlFactory.h"
29 #include "filesystem/Directory.h"
30 #include "filesystem/File.h"
31 #include "filesystem/SpecialProtocol.h"
32 #include "settings/lib/Setting.h"
33 #include "utils/log.h"
34 #include "utils/URIUtils.h"
35 #include "utils/StringUtils.h"
36 #include "windowing/WindowingFactory.h"
37 #include "FileItem.h"
38 #include "URL.h"
39 #include "Util.h"
40
41 using namespace std;
42
43 GUIFontManager::GUIFontManager(void)
44 {
45   m_fontsetUnicode=false;
46   m_canReload = true;
47 }
48
49 GUIFontManager::~GUIFontManager(void)
50 {
51   Clear();
52 }
53
54 void GUIFontManager::RescaleFontSizeAndAspect(float *size, float *aspect, const RESOLUTION_INFO &sourceRes, bool preserveAspect)
55 {
56   // get the UI scaling constants so that we can scale our font sizes correctly
57   // as fonts aren't scaled at render time (due to aliasing) we must scale
58   // the size of the fonts before they are drawn to bitmaps
59   float scaleX, scaleY;
60   g_graphicsContext.GetGUIScaling(sourceRes, scaleX, scaleY);
61
62   if (preserveAspect)
63   {
64     // font always displayed in the aspect specified by the aspect parameter
65     *aspect /= g_graphicsContext.GetResInfo().fPixelRatio;
66   }
67   else
68   {
69     // font streched like the rest of the UI, aspect parameter being the original aspect
70
71     // adjust aspect ratio
72     *aspect *= sourceRes.fPixelRatio;
73
74     *aspect *= scaleY / scaleX;
75   }
76
77   *size /= scaleY;
78 }
79
80 static bool CheckFont(CStdString& strPath, const CStdString& newPath,
81                       const CStdString& filename)
82 {
83   if (!XFILE::CFile::Exists(strPath))
84   {
85     strPath = URIUtils::AddFileToFolder(newPath,filename);
86 #ifdef TARGET_POSIX
87     strPath = CSpecialProtocol::TranslatePathConvertCase(strPath);
88 #endif
89     return false;
90   }
91
92   return true;
93 }
94
95 CGUIFont* GUIFontManager::LoadTTF(const CStdString& strFontName, const CStdString& strFilename, color_t textColor, color_t shadowColor, const int iSize, const int iStyle, bool border, float lineSpacing, float aspect, const RESOLUTION_INFO *sourceRes, bool preserveAspect)
96 {
97   float originalAspect = aspect;
98
99   //check if font already exists
100   CGUIFont* pFont = GetFont(strFontName, false);
101   if (pFont)
102     return pFont;
103
104   if (!sourceRes) // no source res specified, so assume the skin res
105     sourceRes = &m_skinResolution;
106
107   float newSize = (float)iSize;
108   RescaleFontSizeAndAspect(&newSize, &aspect, *sourceRes, preserveAspect);
109
110   // First try to load the font from the skin
111   CStdString strPath;
112   if (!CURL::IsFullPath(strFilename))
113   {
114     strPath = URIUtils::AddFileToFolder(g_graphicsContext.GetMediaDir(), "fonts");
115     strPath = URIUtils::AddFileToFolder(strPath, strFilename);
116   }
117   else
118     strPath = strFilename;
119
120 #ifdef TARGET_POSIX
121   strPath = CSpecialProtocol::TranslatePathConvertCase(strPath);
122 #endif
123
124   // Check if the file exists, otherwise try loading it from the global media dir
125   CStdString file = URIUtils::GetFileName(strFilename);
126   if (!CheckFont(strPath,"special://home/media/Fonts",file))
127     CheckFont(strPath,"special://xbmc/media/Fonts",file);
128
129   // check if we already have this font file loaded (font object could differ only by color or style)
130   CStdString TTFfontName = StringUtils::Format("%s_%f_%f%s", strFilename.c_str(), newSize, aspect, border ? "_border" : "");
131
132   CGUIFontTTFBase* pFontFile = GetFontFile(TTFfontName);
133   if (!pFontFile)
134   {
135     pFontFile = new CGUIFontTTF(TTFfontName);
136     bool bFontLoaded = pFontFile->Load(strPath, newSize, aspect, 1.0f, border);
137
138     if (!bFontLoaded)
139     {
140       delete pFontFile;
141
142       // font could not be loaded - try Arial.ttf, which we distribute
143       if (strFilename != "arial.ttf")
144       {
145         CLog::Log(LOGERROR, "Couldn't load font name: %s(%s), trying to substitute arial.ttf", strFontName.c_str(), strFilename.c_str());
146         return LoadTTF(strFontName, "arial.ttf", textColor, shadowColor, iSize, iStyle, border, lineSpacing, originalAspect);
147       }
148       CLog::Log(LOGERROR, "Couldn't load font name:%s file:%s", strFontName.c_str(), strPath.c_str());
149
150       return NULL;
151     }
152
153     m_vecFontFiles.push_back(pFontFile);
154   }
155
156   // font file is loaded, create our CGUIFont
157   CGUIFont *pNewFont = new CGUIFont(strFontName, iStyle, textColor, shadowColor, lineSpacing, (float)iSize, pFontFile);
158   m_vecFonts.push_back(pNewFont);
159
160   // Store the original TTF font info in case we need to reload it in a different resolution
161   OrigFontInfo fontInfo;
162   fontInfo.size = iSize;
163   fontInfo.aspect = originalAspect;
164   fontInfo.fontFilePath = strPath;
165   fontInfo.fileName = strFilename;
166   fontInfo.sourceRes = *sourceRes;
167   fontInfo.preserveAspect = preserveAspect;
168   fontInfo.border = border;
169   m_vecFontInfo.push_back(fontInfo);
170
171   return pNewFont;
172 }
173
174 bool GUIFontManager::OnMessage(CGUIMessage &message)
175 {
176   if (message.GetMessage() != GUI_MSG_NOTIFY_ALL)
177     return false;
178
179   if (message.GetParam1() == GUI_MSG_RENDERER_LOST)
180   {
181     m_canReload = false;
182     return true;
183   }
184
185   if (message.GetParam1() == GUI_MSG_RENDERER_RESET)
186   { // our device has been reset - we have to reload our ttf fonts, and send
187     // a message to controls that we have done so
188     ReloadTTFFonts();
189     g_windowManager.SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_WINDOW_RESIZE);
190     m_canReload = true;
191     return true;
192   }
193
194   if (message.GetParam1() == GUI_MSG_WINDOW_RESIZE)
195   { // we need to reload our fonts
196     if (m_canReload)
197     {
198       ReloadTTFFonts();
199       // no need to send a resize message, as this message will do the rounds
200       return true;
201     }
202   }
203   return false;
204 }
205
206 void GUIFontManager::ReloadTTFFonts(void)
207 {
208   if (!m_vecFonts.size())
209     return;   // we haven't even loaded fonts in yet
210
211   for (unsigned int i = 0; i < m_vecFonts.size(); i++)
212   {
213     CGUIFont* font = m_vecFonts[i];
214     OrigFontInfo fontInfo = m_vecFontInfo[i];
215
216     float aspect = fontInfo.aspect;
217     float newSize = (float)fontInfo.size;
218     CStdString& strPath = fontInfo.fontFilePath;
219     CStdString& strFilename = fontInfo.fileName;
220
221     RescaleFontSizeAndAspect(&newSize, &aspect, fontInfo.sourceRes, fontInfo.preserveAspect);
222
223     CStdString TTFfontName = StringUtils::Format("%s_%f_%f%s", strFilename.c_str(), newSize, aspect, fontInfo.border ? "_border" : "");
224     CGUIFontTTFBase* pFontFile = GetFontFile(TTFfontName);
225     if (!pFontFile)
226     {
227       pFontFile = new CGUIFontTTF(TTFfontName);
228       if (!pFontFile || !pFontFile->Load(strPath, newSize, aspect, 1.0f, fontInfo.border))
229       {
230         delete pFontFile;
231         // font could not be loaded
232         CLog::Log(LOGERROR, "Couldn't re-load font file:%s", strPath.c_str());
233         return;
234       }
235
236       m_vecFontFiles.push_back(pFontFile);
237     }
238
239     font->SetFont(pFontFile);
240   }
241 }
242
243 void GUIFontManager::Unload(const CStdString& strFontName)
244 {
245   for (vector<CGUIFont*>::iterator iFont = m_vecFonts.begin(); iFont != m_vecFonts.end(); ++iFont)
246   {
247     if ((*iFont)->GetFontName().Equals(strFontName))
248     {
249       delete (*iFont);
250       m_vecFonts.erase(iFont);
251       return;
252     }
253   }
254 }
255
256 void GUIFontManager::FreeFontFile(CGUIFontTTFBase *pFont)
257 {
258   for (vector<CGUIFontTTFBase*>::iterator it = m_vecFontFiles.begin(); it != m_vecFontFiles.end(); ++it)
259   {
260     if (pFont == *it)
261     {
262       m_vecFontFiles.erase(it);
263       delete pFont;
264       return;
265     }
266   }
267 }
268
269 CGUIFontTTFBase* GUIFontManager::GetFontFile(const CStdString& strFileName)
270 {
271   for (int i = 0; i < (int)m_vecFontFiles.size(); ++i)
272   {
273     CGUIFontTTFBase* pFont = (CGUIFontTTFBase *)m_vecFontFiles[i];
274     if (pFont->GetFileName().Equals(strFileName))
275       return pFont;
276   }
277   return NULL;
278 }
279
280 CGUIFont* GUIFontManager::GetFont(const CStdString& strFontName, bool fallback /*= true*/)
281 {
282   for (int i = 0; i < (int)m_vecFonts.size(); ++i)
283   {
284     CGUIFont* pFont = m_vecFonts[i];
285     if (pFont->GetFontName().Equals(strFontName))
286       return pFont;
287   }
288   // fall back to "font13" if we have none
289   if (fallback && !strFontName.empty() && !strFontName.Equals("-") && !strFontName.Equals("font13"))
290     return GetFont("font13");
291   return NULL;
292 }
293
294 CGUIFont* GUIFontManager::GetDefaultFont(bool border)
295 {
296   // first find "font13" or "__defaultborder__"
297   unsigned int font13index = m_vecFonts.size();
298   CGUIFont *font13border = NULL;
299   for (unsigned int i = 0; i < m_vecFonts.size(); i++)
300   {
301     CGUIFont* font = m_vecFonts[i];
302     if (font->GetFontName() == "font13")
303       font13index = i;
304     else if (font->GetFontName() == "__defaultborder__")
305       font13border = font;
306   }
307   // no "font13" means no default font is found - use the first font found.
308   if (font13index == m_vecFonts.size())
309   {
310     if (m_vecFonts.empty())
311       return NULL;
312     font13index = 0;
313   }
314
315   if (border)
316   {
317     if (!font13border)
318     { // create it
319       CGUIFont *font13 = m_vecFonts[font13index];
320       OrigFontInfo fontInfo = m_vecFontInfo[font13index];
321       font13border = LoadTTF("__defaultborder__", fontInfo.fileName, 0xFF000000, 0, fontInfo.size, font13->GetStyle(), true, 1.0f, fontInfo.aspect, &fontInfo.sourceRes, fontInfo.preserveAspect);
322     }
323     return font13border;
324   }
325   return m_vecFonts[font13index];
326 }
327
328 void GUIFontManager::Clear()
329 {
330   for (int i = 0; i < (int)m_vecFonts.size(); ++i)
331   {
332     CGUIFont* pFont = m_vecFonts[i];
333     delete pFont;
334   }
335
336   m_vecFonts.clear();
337   m_vecFontFiles.clear();
338   m_vecFontInfo.clear();
339   m_fontsetUnicode=false;
340 }
341
342 void GUIFontManager::LoadFonts(const CStdString& strFontSet)
343 {
344   CXBMCTinyXML xmlDoc;
345   if (!OpenFontFile(xmlDoc))
346     return;
347
348   TiXmlElement* pRootElement = xmlDoc.RootElement();
349   const TiXmlNode *pChild = pRootElement->FirstChild();
350
351   // If there are no fontset's defined in the XML (old skin format) run in backward compatibility
352   // and ignore the fontset request
353   CStdString strValue = pChild->Value();
354   if (strValue == "fontset")
355   {
356     CStdString foundTTF;
357     while (pChild)
358     {
359       strValue = pChild->Value();
360       if (strValue == "fontset")
361       {
362         const char* idAttr = ((TiXmlElement*) pChild)->Attribute("id");
363
364         const char* unicodeAttr = ((TiXmlElement*) pChild)->Attribute("unicode");
365
366         if (foundTTF.empty() && idAttr != NULL && unicodeAttr != NULL && stricmp(unicodeAttr, "true") == 0)
367           foundTTF = idAttr;
368
369         // Check if this is the fontset that we want
370         if (idAttr != NULL && stricmp(strFontSet.c_str(), idAttr) == 0)
371         {
372           m_fontsetUnicode=false;
373           // Check if this is the a ttf fontset
374           if (unicodeAttr != NULL && stricmp(unicodeAttr, "true") == 0)
375             m_fontsetUnicode=true;
376
377           if (m_fontsetUnicode)
378           {
379             LoadFonts(pChild->FirstChild());
380             break;
381           }
382         }
383
384       }
385
386       pChild = pChild->NextSibling();
387     }
388
389     // If no fontset was loaded
390     if (pChild == NULL)
391     {
392       CLog::Log(LOGWARNING, "file doesnt have <fontset> with name '%s', defaulting to first fontset", strFontSet.c_str());
393       if (!foundTTF.empty())
394         LoadFonts(foundTTF);
395     }
396   }
397   else
398   {
399     CLog::Log(LOGERROR, "file doesnt have <fontset> in <fonts>, but rather %s", strValue.c_str());
400     return ;
401   }
402 }
403
404 void GUIFontManager::LoadFonts(const TiXmlNode* fontNode)
405 {
406   while (fontNode)
407   {
408     CStdString strValue = fontNode->Value();
409     if (strValue == "font")
410     {
411       const TiXmlNode *pNode = fontNode->FirstChild("name");
412       if (pNode)
413       {
414         CStdString strFontName = pNode->FirstChild()->Value();
415         color_t shadowColor = 0;
416         color_t textColor = 0;
417         CGUIControlFactory::GetColor(fontNode, "shadow", shadowColor);
418         CGUIControlFactory::GetColor(fontNode, "color", textColor);
419         const TiXmlNode *pNode = fontNode->FirstChild("filename");
420         if (pNode)
421         {
422           CStdString strFontFileName = pNode->FirstChild()->Value();
423           StringUtils::ToLower(strFontFileName);
424           if (strFontFileName.find(".ttf") != string::npos)
425           {
426             int iSize = 20;
427             int iStyle = FONT_STYLE_NORMAL;
428             float aspect = 1.0f;
429             float lineSpacing = 1.0f;
430
431             XMLUtils::GetInt(fontNode, "size", iSize);
432             if (iSize <= 0) iSize = 20;
433
434             pNode = fontNode->FirstChild("style");
435             if (pNode && pNode->FirstChild())
436             {
437               vector<string> styles = StringUtils::Split(pNode->FirstChild()->ValueStr(), " ");
438               for (vector<string>::iterator i = styles.begin(); i != styles.end(); ++i)
439               {
440                 if (*i == "bold")
441                   iStyle |= FONT_STYLE_BOLD;
442                 else if (*i == "italics")
443                   iStyle |= FONT_STYLE_ITALICS;
444                 else if (*i == "bolditalics") // backward compatibility
445                   iStyle |= (FONT_STYLE_BOLD | FONT_STYLE_ITALICS);
446                 else if (*i == "uppercase")
447                   iStyle |= FONT_STYLE_UPPERCASE;
448                 else if (*i == "lowercase")
449                   iStyle |= FONT_STYLE_LOWERCASE;
450               }
451             }
452
453             XMLUtils::GetFloat(fontNode, "linespacing", lineSpacing);
454             XMLUtils::GetFloat(fontNode, "aspect", aspect);
455
456             LoadTTF(strFontName, strFontFileName, textColor, shadowColor, iSize, iStyle, false, lineSpacing, aspect);
457           }
458         }
459       }
460     }
461
462     fontNode = fontNode->NextSibling();
463   }
464 }
465
466 bool GUIFontManager::OpenFontFile(CXBMCTinyXML& xmlDoc)
467 {
468   // Get the file to load fonts from:
469   CStdString strPath = g_SkinInfo->GetSkinPath("Font.xml", &m_skinResolution);
470   CLog::Log(LOGINFO, "Loading fonts from %s", strPath.c_str());
471
472   // first try our preferred file
473   if ( !xmlDoc.LoadFile(strPath) )
474   {
475     CLog::Log(LOGERROR, "Couldn't load %s", strPath.c_str());
476     return false;
477   }
478   TiXmlElement* pRootElement = xmlDoc.RootElement();
479
480   CStdString strValue = pRootElement->Value();
481   if (strValue != CStdString("fonts"))
482   {
483     CLog::Log(LOGERROR, "file %s doesnt start with <fonts>", strPath.c_str());
484     return false;
485   }
486
487   return true;
488 }
489
490 bool GUIFontManager::GetFirstFontSetUnicode(CStdString& strFontSet)
491 {
492   strFontSet.clear();
493
494   // Load our font file
495   CXBMCTinyXML xmlDoc;
496   if (!OpenFontFile(xmlDoc))
497     return false;
498
499   TiXmlElement* pRootElement = xmlDoc.RootElement();
500   const TiXmlNode *pChild = pRootElement->FirstChild();
501
502   CStdString strValue = pChild->Value();
503   if (strValue == "fontset")
504   {
505     while (pChild)
506     {
507       strValue = pChild->Value();
508       if (strValue == "fontset")
509       {
510         const char* idAttr = ((TiXmlElement*) pChild)->Attribute("id");
511
512         const char* unicodeAttr = ((TiXmlElement*) pChild)->Attribute("unicode");
513
514         // Check if this is a fontset with a ttf attribute set to true
515         if (unicodeAttr != NULL && stricmp(unicodeAttr, "true") == 0)
516         {
517           //  This is the first ttf fontset
518           strFontSet=idAttr;
519           break;
520         }
521
522       }
523
524       pChild = pChild->NextSibling();
525     }
526
527     // If no fontset was loaded
528     if (pChild == NULL)
529       CLog::Log(LOGWARNING, "file doesnt have <fontset> with attribute unicode=\"true\"");
530   }
531   else
532   {
533     CLog::Log(LOGERROR, "file doesnt have <fontset> in <fonts>, but rather %s", strValue.c_str());
534   }
535
536   return !strFontSet.empty();
537 }
538
539 bool GUIFontManager::IsFontSetUnicode(const CStdString& strFontSet)
540 {
541   CXBMCTinyXML xmlDoc;
542   if (!OpenFontFile(xmlDoc))
543     return false;
544
545   TiXmlElement* pRootElement = xmlDoc.RootElement();
546   const TiXmlNode *pChild = pRootElement->FirstChild();
547
548   CStdString strValue = pChild->Value();
549   if (strValue == "fontset")
550   {
551     while (pChild)
552     {
553       strValue = pChild->Value();
554       if (strValue == "fontset")
555       {
556         const char* idAttr = ((TiXmlElement*) pChild)->Attribute("id");
557
558         const char* unicodeAttr = ((TiXmlElement*) pChild)->Attribute("unicode");
559
560         // Check if this is the fontset that we want
561         if (idAttr != NULL && stricmp(strFontSet.c_str(), idAttr) == 0)
562           return (unicodeAttr != NULL && stricmp(unicodeAttr, "true") == 0);
563
564       }
565
566       pChild = pChild->NextSibling();
567     }
568   }
569
570   return false;
571 }
572
573 void GUIFontManager::SettingOptionsFontsFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string &current)
574 {
575   CFileItemList items;
576   CFileItemList items2;
577
578   // find TTF fonts
579   XFILE::CDirectory::GetDirectory("special://home/media/Fonts/", items2);
580
581   if (XFILE::CDirectory::GetDirectory("special://xbmc/media/Fonts/", items))
582   {
583     items.Append(items2);
584     for (int i = 0; i < items.Size(); ++i)
585     {
586       CFileItemPtr pItem = items[i];
587
588       if (!pItem->m_bIsFolder
589           && URIUtils::HasExtension(pItem->GetLabel(), ".ttf"))
590       {
591         list.push_back(make_pair(pItem->GetLabel(), pItem->GetLabel()));
592       }
593     }
594   }
595 }