[release] version bump to 13.0 beta1
[vuplus_xbmc] / xbmc / guilib / GUIFont.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 "GUIFont.h"
22 #include "GUIFontTTF.h"
23 #include "GraphicContext.h"
24
25 #include "threads/SingleLock.h"
26 #include "utils/TimeUtils.h"
27 #include "utils/MathUtils.h"
28
29 #include "utils/CharsetConverter.h"
30
31 #define ROUND(x) (float)(MathUtils::round_int(x))
32
33 CScrollInfo::CScrollInfo(unsigned int wait /* = 50 */, float pos /* = 0 */,
34   int speed /* = defaultSpeed */, const CStdString &scrollSuffix /* = " | " */)
35 {
36     initialWait = wait;
37     initialPos = pos;
38     SetSpeed(speed ? speed : defaultSpeed);
39     g_charsetConverter.utf8ToW(scrollSuffix, suffix);
40     Reset();
41 }
42
43 float CScrollInfo::GetPixelsPerFrame()
44 {
45   static const float alphaEMA = 0.05f;
46
47   if (0 == pixelSpeed)
48     return 0; // not scrolling
49   unsigned int currentTime = CTimeUtils::GetFrameTime();
50   float delta = m_lastFrameTime ? (float)(currentTime - m_lastFrameTime) : m_averageFrameTime;
51   if (delta > 100)
52     delta = 100; // assume a minimum of 10 fps
53   m_lastFrameTime = currentTime;
54   // do an exponential moving average of the frame time
55   m_averageFrameTime = m_averageFrameTime + (delta - m_averageFrameTime) * alphaEMA;
56   // and multiply by pixel speed (per ms) to get number of pixels to move this frame
57   return pixelSpeed * m_averageFrameTime;
58 }
59
60 CGUIFont::CGUIFont(const CStdString& strFontName, uint32_t style, color_t textColor,
61                    color_t shadowColor, float lineSpacing, float origHeight, CGUIFontTTFBase *font)
62 {
63   m_strFontName = strFontName;
64   m_style = style & FONT_STYLE_MASK;
65   m_textColor = textColor;
66   m_shadowColor = shadowColor;
67   m_lineSpacing = lineSpacing;
68   m_origHeight = origHeight;
69   m_font = font;
70
71   if (m_font)
72     m_font->AddReference();
73 }
74
75 CGUIFont::~CGUIFont()
76 {
77   if (m_font)
78     m_font->RemoveReference();
79 }
80
81 CStdString& CGUIFont::GetFontName()
82 {
83   return m_strFontName;
84 }
85
86 void CGUIFont::DrawText( float x, float y, const vecColors &colors, color_t shadowColor,
87                 const vecText &text, uint32_t alignment, float maxPixelWidth)
88 {
89   if (!m_font) return;
90
91   bool clip = maxPixelWidth > 0;
92   if (clip && ClippedRegionIsEmpty(x, y, maxPixelWidth, alignment))
93     return;
94
95   maxPixelWidth = ROUND(maxPixelWidth / g_graphicsContext.GetGUIScaleX());
96   vecColors renderColors;
97   for (unsigned int i = 0; i < colors.size(); i++)
98     renderColors.push_back(g_graphicsContext.MergeAlpha(colors[i] ? colors[i] : m_textColor));
99   if (!shadowColor) shadowColor = m_shadowColor;
100   if (shadowColor)
101   {
102     shadowColor = g_graphicsContext.MergeAlpha(shadowColor);
103     vecColors shadowColors;
104     for (unsigned int i = 0; i < renderColors.size(); i++)
105       shadowColors.push_back((renderColors[i] & 0xff000000) != 0 ? shadowColor : 0);
106     m_font->DrawTextInternal(x + 1, y + 1, shadowColors, text, alignment, maxPixelWidth, false);
107   }
108   m_font->DrawTextInternal( x, y, renderColors, text, alignment, maxPixelWidth, false);
109
110   if (clip)
111     g_graphicsContext.RestoreClipRegion();
112 }
113
114 bool CGUIFont::UpdateScrollInfo(const vecText &text, CScrollInfo &scrollInfo)
115 {
116   // draw at our scroll position
117   // we handle the scrolling as follows:
118   //   We scroll on a per-pixel basis up until we have scrolled the first character outside
119   //   of our viewport, whereby we cycle the string around, and reset the scroll position.
120   //
121   //   pixelPos is the amount in pixels to move the string by.
122   //   characterPos is the amount in characters to rotate the string by.
123   //
124   if (scrollInfo.waitTime)
125   {
126     scrollInfo.waitTime--;
127     return false;
128   }
129
130   if (text.empty())
131     return false;
132
133   CScrollInfo old(scrollInfo);
134
135   // move along by the appropriate scroll amount
136   float scrollAmount = fabs(scrollInfo.GetPixelsPerFrame() * g_graphicsContext.GetGUIScaleX());
137
138   if (scrollInfo.pixelSpeed > 0)
139   {
140     // we want to move scrollAmount, grab the next character
141     float charWidth = GetCharWidth(scrollInfo.GetCurrentChar(text));
142     if (scrollInfo.pixelPos + scrollAmount < charWidth)
143       scrollInfo.pixelPos += scrollAmount;  // within the current character
144     else
145     { // past the current character, decrement scrollAmount by the charWidth and move to the next character
146       while (scrollInfo.pixelPos + scrollAmount >= charWidth)
147       {
148         scrollAmount -= (charWidth - scrollInfo.pixelPos);
149         scrollInfo.pixelPos = 0;
150         scrollInfo.characterPos++;
151         if (scrollInfo.characterPos >= text.size() + scrollInfo.suffix.size())
152         {
153           scrollInfo.Reset();
154           break;
155         }
156         charWidth = GetCharWidth(scrollInfo.GetCurrentChar(text));
157       }
158     }
159   }
160   else if (scrollInfo.pixelSpeed < 0)
161   { // scrolling backwards
162     // we want to move scrollAmount, grab the next character
163     float charWidth = GetCharWidth(scrollInfo.GetCurrentChar(text));
164     if (scrollInfo.pixelPos + scrollAmount < charWidth)
165       scrollInfo.pixelPos += scrollAmount;  // within the current character
166     else
167     { // past the current character, decrement scrollAmount by the charWidth and move to the next character
168       while (scrollInfo.pixelPos + scrollAmount >= charWidth)
169       {
170         scrollAmount -= (charWidth - scrollInfo.pixelPos);
171         scrollInfo.pixelPos = 0;
172         if (scrollInfo.characterPos == 0)
173         {
174           scrollInfo.Reset();
175           scrollInfo.characterPos = text.size() + scrollInfo.suffix.size() - 1;
176           break;
177         }
178         scrollInfo.characterPos--;
179         charWidth = GetCharWidth(scrollInfo.GetCurrentChar(text));
180       }
181     }
182   }
183
184   if(scrollInfo.characterPos != old.characterPos
185   || scrollInfo.pixelPos     != old.pixelPos)
186     return true;
187   else
188     return false;
189 }
190
191 void CGUIFont::DrawScrollingText(float x, float y, const vecColors &colors, color_t shadowColor,
192                 const vecText &text, uint32_t alignment, float maxWidth, const CScrollInfo &scrollInfo)
193 {
194   if (!m_font) return;
195   if (!shadowColor) shadowColor = m_shadowColor;
196
197   float spaceWidth = GetCharWidth(L' ');
198   // max chars on screen + extra margin chars
199   vecText::size_type maxChars =
200     std::min<vecText::size_type>(
201       (text.size() + (vecText::size_type)scrollInfo.suffix.size()),
202       (vecText::size_type)((maxWidth * 1.05f) / spaceWidth));
203
204   if (!text.size() || ClippedRegionIsEmpty(x, y, maxWidth, alignment))
205     return; // nothing to render
206
207   maxWidth = ROUND(maxWidth / g_graphicsContext.GetGUIScaleX());
208
209   float charWidth = GetCharWidth(scrollInfo.GetCurrentChar(text));
210   float offset;
211   if(scrollInfo.pixelSpeed >= 0)
212     offset = scrollInfo.pixelPos;
213   else
214     offset = charWidth - scrollInfo.pixelPos;
215
216   // Now rotate our string as needed, only take a slightly larger then visible part of the text.
217   unsigned int pos = scrollInfo.characterPos;
218   vecText renderText;
219   renderText.reserve(maxChars);
220   for (vecText::size_type i = 0; i < maxChars; i++)
221   {
222     if (pos >= text.size() + scrollInfo.suffix.size())
223       pos = 0;
224     if (pos < text.size())
225       renderText.push_back(text[pos]);
226     else
227       renderText.push_back(scrollInfo.suffix[pos - text.size()]);
228     pos++;
229   }
230
231   vecColors renderColors;
232   for (unsigned int i = 0; i < colors.size(); i++)
233     renderColors.push_back(g_graphicsContext.MergeAlpha(colors[i] ? colors[i] : m_textColor));
234
235   bool scroll =  !scrollInfo.waitTime && scrollInfo.pixelSpeed;
236   if (shadowColor)
237   {
238     shadowColor = g_graphicsContext.MergeAlpha(shadowColor);
239     vecColors shadowColors;
240     for (unsigned int i = 0; i < renderColors.size(); i++)
241       shadowColors.push_back((renderColors[i] & 0xff000000) != 0 ? shadowColor : 0);
242     m_font->DrawTextInternal(x - offset + 1, y + 1, shadowColors, renderText, alignment, maxWidth + scrollInfo.pixelPos + m_font->GetLineHeight(2.0f), scroll);
243   }
244   m_font->DrawTextInternal(x - offset, y, renderColors, renderText, alignment, maxWidth + scrollInfo.pixelPos + m_font->GetLineHeight(2.0f), scroll);
245
246   g_graphicsContext.RestoreClipRegion();
247 }
248
249 // remaps unsupported font glpyhs to other suitable ones
250 wchar_t CGUIFont::RemapGlyph(wchar_t letter)
251 {
252   if (letter == 0x2019 || letter == 0x2018) return 0x0027;  // single quotes
253   else if (letter == 0x201c || letter == 0x201d) return 0x0022;
254   return 0; // no decent character map
255 }
256
257 bool CGUIFont::ClippedRegionIsEmpty(float x, float y, float width, uint32_t alignment) const
258 {
259   if (alignment & XBFONT_CENTER_X)
260     x -= width * 0.5f;
261   else if (alignment & XBFONT_RIGHT)
262     x -= width;
263   if (alignment & XBFONT_CENTER_Y)
264     y -= m_font->GetLineHeight(m_lineSpacing);
265
266   return !g_graphicsContext.SetClipRegion(x, y, width, m_font->GetTextHeight(1, 2) * g_graphicsContext.GetGUIScaleY());
267 }
268
269 float CGUIFont::GetTextWidth( const vecText &text )
270 {
271   if (!m_font) return 0;
272   CSingleLock lock(g_graphicsContext);
273   return m_font->GetTextWidthInternal(text.begin(), text.end()) * g_graphicsContext.GetGUIScaleX();
274 }
275
276 float CGUIFont::GetCharWidth( character_t ch )
277 {
278   if (!m_font) return 0;
279   CSingleLock lock(g_graphicsContext);
280   return m_font->GetCharWidthInternal(ch) * g_graphicsContext.GetGUIScaleX();
281 }
282
283 float CGUIFont::GetTextHeight(int numLines) const
284 {
285   if (!m_font) return 0;
286   return m_font->GetTextHeight(m_lineSpacing, numLines) * g_graphicsContext.GetGUIScaleY();
287 }
288
289 float CGUIFont::GetTextBaseLine() const
290 {
291   if (!m_font) return 0;
292   return m_font->GetTextBaseLine() * g_graphicsContext.GetGUIScaleY();
293 }
294
295 float CGUIFont::GetLineHeight() const
296 {
297   if (!m_font) return 0;
298   return m_font->GetLineHeight(m_lineSpacing) * g_graphicsContext.GetGUIScaleY();
299 }
300
301 float CGUIFont::GetScaleFactor() const
302 {
303   if (!m_font) return 1.0f;
304   return m_font->GetFontHeight() / m_origHeight;
305 }
306
307 void CGUIFont::Begin()
308 {
309   if (!m_font) return;
310   m_font->Begin();
311 }
312
313 void CGUIFont::End()
314 {
315   if (!m_font) return;
316   m_font->End();
317 }
318
319 void CGUIFont::SetFont(CGUIFontTTFBase *font)
320 {
321   if (m_font == font)
322     return; // no need to update the font if we already have it
323   if (m_font)
324     m_font->RemoveReference();
325   m_font = font;
326   if (m_font)
327     m_font->AddReference();
328 }