1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "GUIFont.h"
10 #include "GUIFontTTF.h"
11 #include "GUIFontManager.h"
12 #include "Texture.h"
13 #include "windowing/GraphicContext.h"
14 #include "ServiceBroker.h"
15 #include "filesystem/SpecialProtocol.h"
16 #include "utils/MathUtils.h"
17 #include "utils/log.h"
18 #include "rendering/RenderSystem.h"
19 #include "windowing/WinSystem.h"
20 #include "URL.h"
21 #include "filesystem/File.h"
22 #include "threads/SystemClock.h"
23 
24 #include <math.h>
25 #include <memory>
26 #include <queue>
27 
28 // stuff for freetype
29 #include <ft2build.h>
30 
31 #if defined(HAS_GL) || defined(HAS_GLES)
32 #include "system_gl.h"
33 #endif
34 
35 #if defined(HAS_DX)
36 #include "guilib/D3DResource.h"
37 #endif
38 
39 #ifdef TARGET_WINDOWS_STORE
40 #define generic GenericFromFreeTypeLibrary
41 #endif
42 
43 #include FT_FREETYPE_H
44 #include FT_GLYPH_H
45 #include FT_OUTLINE_H
46 #include FT_STROKER_H
47 
48 #define CHARS_PER_TEXTURE_LINE 20 // number of characters to cache per texture line
49 #define CHAR_CHUNK    64      // 64 chars allocated at a time (1024 bytes)
50 #define GLYPH_STRENGTH_BOLD 24
51 #define GLYPH_STRENGTH_LIGHT -48
52 
53 
54 class CFreeTypeLibrary
55 {
56 public:
CFreeTypeLibrary()57   CFreeTypeLibrary()
58   {
59     m_library = NULL;
60   }
61 
~CFreeTypeLibrary()62   virtual ~CFreeTypeLibrary()
63   {
64     if (m_library)
65       FT_Done_FreeType(m_library);
66   }
67 
GetFont(const std::string & filename,float size,float aspect,XUTILS::auto_buffer & memoryBuf)68   FT_Face GetFont(const std::string &filename, float size, float aspect, XUTILS::auto_buffer& memoryBuf)
69   {
70     // don't have it yet - create it
71     if (!m_library)
72       FT_Init_FreeType(&m_library);
73     if (!m_library)
74     {
75       CLog::Log(LOGERROR, "Unable to initialize freetype library");
76       return NULL;
77     }
78 
79     FT_Face face;
80 
81     // ok, now load the font face
82     CURL realFile(CSpecialProtocol::TranslatePath(filename));
83     if (realFile.GetFileName().empty())
84       return NULL;
85 
86     memoryBuf.clear();
87 #ifndef TARGET_WINDOWS
88     if (!realFile.GetProtocol().empty())
89 #endif // ! TARGET_WINDOWS
90     {
91       // load file into memory if it is not on local drive
92       // in case of win32: always load file into memory as filename is in UTF-8,
93       //                   but freetype expect filename in ANSI encoding
94       XFILE::CFile f;
95       if (f.LoadFile(realFile, memoryBuf) <= 0)
96         return NULL;
97       if (FT_New_Memory_Face(m_library, (const FT_Byte*)memoryBuf.get(), memoryBuf.size(), 0, &face) != 0)
98         return NULL;
99     }
100 #ifndef TARGET_WINDOWS
101     else if (FT_New_Face( m_library, realFile.GetFileName().c_str(), 0, &face ))
102       return NULL;
103 #endif // ! TARGET_WINDOWS
104 
105     unsigned int ydpi = 72; // 72 points to the inch is the freetype default
106     unsigned int xdpi = (unsigned int)MathUtils::round_int(ydpi * aspect);
107 
108     // we set our screen res currently to 96dpi in both directions (windows default)
109     // we cache our characters (for rendering speed) so it's probably
110     // not a good idea to allow free scaling of fonts - rather, just
111     // scaling to pixel ratio on screen perhaps?
112     if (FT_Set_Char_Size( face, 0, (int)(size*64 + 0.5f), xdpi, ydpi ))
113     {
114       FT_Done_Face(face);
115       return NULL;
116     }
117 
118     return face;
119   };
120 
GetStroker()121   FT_Stroker GetStroker()
122   {
123     if (!m_library)
124       return NULL;
125 
126     FT_Stroker stroker;
127     if (FT_Stroker_New(m_library, &stroker))
128       return NULL;
129 
130     return stroker;
131   };
132 
ReleaseFont(FT_Face face)133   static void ReleaseFont(FT_Face face)
134   {
135     assert(face);
136     FT_Done_Face(face);
137   };
138 
ReleaseStroker(FT_Stroker stroker)139   static void ReleaseStroker(FT_Stroker stroker)
140   {
141     assert(stroker);
142     FT_Stroker_Done(stroker);
143   }
144 
145 private:
146   FT_Library   m_library;
147 };
148 
149 XBMC_GLOBAL_REF(CFreeTypeLibrary, g_freeTypeLibrary); // our freetype library
150 #define g_freeTypeLibrary XBMC_GLOBAL_USE(CFreeTypeLibrary)
151 
CGUIFontTTF(const std::string & strFileName)152 CGUIFontTTF::CGUIFontTTF(const std::string& strFileName)
153   : m_staticCache(*this), m_dynamicCache(*this)
154 {
155   m_texture = NULL;
156   m_char = NULL;
157   m_maxChars = 0;
158   m_nestedBeginCount = 0;
159 
160   m_vertex.reserve(4*1024);
161 
162   m_face = NULL;
163   m_stroker = NULL;
164   memset(m_charquick, 0, sizeof(m_charquick));
165   m_strFileName = strFileName;
166   m_referenceCount = 0;
167   m_originX = m_originY = 0.0f;
168   m_cellBaseLine = m_cellHeight = 0;
169   m_numChars = 0;
170   m_posX = m_posY = 0;
171   m_textureHeight = m_textureWidth = 0;
172   m_textureScaleX = m_textureScaleY = 0.0;
173   m_ellipsesWidth = m_height = 0.0f;
174   m_color = 0;
175   m_nTexture = 0;
176 
177   m_renderSystem = CServiceBroker::GetRenderSystem();
178 }
179 
~CGUIFontTTF(void)180 CGUIFontTTF::~CGUIFontTTF(void)
181 {
182   Clear();
183 }
184 
AddReference()185 void CGUIFontTTF::AddReference()
186 {
187   m_referenceCount++;
188 }
189 
RemoveReference()190 void CGUIFontTTF::RemoveReference()
191 {
192   // delete this object when it's reference count hits zero
193   m_referenceCount--;
194   if (!m_referenceCount)
195     g_fontManager.FreeFontFile(this);
196 }
197 
198 
ClearCharacterCache()199 void CGUIFontTTF::ClearCharacterCache()
200 {
201   delete(m_texture);
202 
203   DeleteHardwareTexture();
204 
205   m_texture = NULL;
206   delete[] m_char;
207   m_char = new Character[CHAR_CHUNK];
208   memset(m_charquick, 0, sizeof(m_charquick));
209   m_numChars = 0;
210   m_maxChars = CHAR_CHUNK;
211   // set the posX and posY so that our texture will be created on first character write.
212   m_posX = m_textureWidth;
213   m_posY = -(int)GetTextureLineHeight();
214   m_textureHeight = 0;
215 }
216 
Clear()217 void CGUIFontTTF::Clear()
218 {
219   delete(m_texture);
220   m_texture = NULL;
221   delete[] m_char;
222   memset(m_charquick, 0, sizeof(m_charquick));
223   m_char = NULL;
224   m_maxChars = 0;
225   m_numChars = 0;
226   m_posX = 0;
227   m_posY = 0;
228   m_nestedBeginCount = 0;
229 
230   if (m_face)
231     g_freeTypeLibrary.ReleaseFont(m_face);
232   m_face = NULL;
233   if (m_stroker)
234     g_freeTypeLibrary.ReleaseStroker(m_stroker);
235   m_stroker = NULL;
236 
237   m_vertexTrans.clear();
238   m_vertex.clear();
239 
240   m_strFileName.clear();
241   m_fontFileInMemory.clear();
242 }
243 
Load(const std::string & strFilename,float height,float aspect,float lineSpacing,bool border)244 bool CGUIFontTTF::Load(
245     const std::string& strFilename, float height, float aspect, float lineSpacing, bool border)
246 {
247   // we now know that this object is unique - only the GUIFont objects are non-unique, so no need
248   // for reference tracking these fonts
249   m_face = g_freeTypeLibrary.GetFont(strFilename, height, aspect, m_fontFileInMemory);
250 
251   if (!m_face)
252     return false;
253 
254   /*
255    the values used are described below
256 
257       XBMC coords                                     Freetype coords
258 
259                 0  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _  bbox.yMax, ascender
260                         A                 \
261                        A A                |
262                       A   A               |
263                       AAAAA  pppp   cellAscender
264                       A   A  p   p        |
265                       A   A  p   p        |
266    m_cellBaseLine  _ _A_ _A_ pppp_ _ _ _ _/_ _ _ _ _  0, base line.
267                              p            \
268                              p      cellDescender
269      m_cellHeight  _ _ _ _ _ p _ _ _ _ _ _/_ _ _ _ _  bbox.yMin, descender
270 
271    */
272   int cellDescender = std::min<int>(m_face->bbox.yMin, m_face->descender);
273   int cellAscender  = std::max<int>(m_face->bbox.yMax, m_face->ascender);
274 
275   if (border)
276   {
277     /*
278      add on the strength of any border - the non-bordered font needs
279      aligning with the bordered font by utilising GetTextBaseLine()
280      */
281     FT_Pos strength = FT_MulFix( m_face->units_per_EM, m_face->size->metrics.y_scale) / 12;
282     if (strength < 128)
283       strength = 128;
284 
285     cellDescender -= strength;
286     cellAscender  += strength;
287 
288     m_stroker = g_freeTypeLibrary.GetStroker();
289     if (m_stroker)
290       FT_Stroker_Set(m_stroker, strength, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
291   }
292 
293   // scale to pixel sizing, rounding so that maximal extent is obtained
294   float scaler  = height / m_face->units_per_EM;
295   cellDescender = MathUtils::round_int(cellDescender * scaler - 0.5f);   // round down
296   cellAscender  = MathUtils::round_int(cellAscender  * scaler + 0.5f);   // round up
297 
298   m_cellBaseLine = cellAscender;
299   m_cellHeight   = cellAscender - cellDescender;
300 
301   m_height = height;
302 
303   delete(m_texture);
304   m_texture = NULL;
305   delete[] m_char;
306   m_char = NULL;
307 
308   m_maxChars = 0;
309   m_numChars = 0;
310 
311   m_strFilename = strFilename;
312 
313   m_textureHeight = 0;
314   m_textureWidth = ((m_cellHeight * CHARS_PER_TEXTURE_LINE) & ~63) + 64;
315 
316   m_textureWidth = CTexture::PadPow2(m_textureWidth);
317 
318   if (m_textureWidth > m_renderSystem->GetMaxTextureSize())
319     m_textureWidth = m_renderSystem->GetMaxTextureSize();
320   m_textureScaleX = 1.0f / m_textureWidth;
321 
322   // set the posX and posY so that our texture will be created on first character write.
323   m_posX = m_textureWidth;
324   m_posY = -(int)GetTextureLineHeight();
325 
326   // cache the ellipses width
327   Character *ellipse = GetCharacter(L'.');
328   if (ellipse) m_ellipsesWidth = ellipse->advance;
329 
330   return true;
331 }
332 
Begin()333 void CGUIFontTTF::Begin()
334 {
335   if (m_nestedBeginCount == 0 && m_texture != NULL && FirstBegin())
336   {
337     m_vertexTrans.clear();
338     m_vertex.clear();
339   }
340   // Keep track of the nested begin/end calls.
341   m_nestedBeginCount++;
342 }
343 
End()344 void CGUIFontTTF::End()
345 {
346   if (m_nestedBeginCount == 0)
347     return;
348 
349   if (--m_nestedBeginCount > 0)
350     return;
351 
352   LastEnd();
353 }
354 
DrawTextInternal(float x,float y,const std::vector<UTILS::Color> & colors,const vecText & text,uint32_t alignment,float maxPixelWidth,bool scrolling)355 void CGUIFontTTF::DrawTextInternal(float x,
356                                    float y,
357                                    const std::vector<UTILS::Color>& colors,
358                                    const vecText& text,
359                                    uint32_t alignment,
360                                    float maxPixelWidth,
361                                    bool scrolling)
362 {
363   if (text.empty())
364   {
365     return;
366   }
367 
368   Begin();
369 
370   uint32_t rawAlignment = alignment;
371   bool dirtyCache(false);
372   bool hardwareClipping = m_renderSystem->ScissorsCanEffectClipping();
373   CGUIFontCacheStaticPosition staticPos(x, y);
374   CGUIFontCacheDynamicPosition dynamicPos;
375   if (hardwareClipping)
376   {
377     dynamicPos = CGUIFontCacheDynamicPosition(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(x, y),
378                                               CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(x, y),
379                                               CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(x, y));
380   }
381   CVertexBuffer unusedVertexBuffer;
382   CVertexBuffer &vertexBuffer = hardwareClipping ?
383       m_dynamicCache.Lookup(dynamicPos,
384                             colors, text,
385                             alignment, maxPixelWidth,
386                             scrolling,
387                             XbmcThreads::SystemClockMillis(),
388                             dirtyCache) :
389       unusedVertexBuffer;
390   std::shared_ptr<std::vector<SVertex> > tempVertices = std::make_shared<std::vector<SVertex> >();
391   std::shared_ptr<std::vector<SVertex> > &vertices = hardwareClipping ?
392       tempVertices :
393       static_cast<std::shared_ptr<std::vector<SVertex> >&>(m_staticCache.Lookup(staticPos,
394                            colors, text,
395                            alignment, maxPixelWidth,
396                            scrolling,
397                            XbmcThreads::SystemClockMillis(),
398                            dirtyCache));
399   if (dirtyCache)
400   {
401     // save the origin, which is scaled separately
402     m_originX = x;
403     m_originY = y;
404 
405     // Check if we will really need to truncate or justify the text
406     if ( alignment & XBFONT_TRUNCATED )
407     {
408       if ( maxPixelWidth <= 0.0f || GetTextWidthInternal(text.begin(), text.end()) <= maxPixelWidth)
409         alignment &= ~XBFONT_TRUNCATED;
410     }
411     else if ( alignment & XBFONT_JUSTIFIED )
412     {
413       if ( maxPixelWidth <= 0.0f )
414         alignment &= ~XBFONT_JUSTIFIED;
415     }
416 
417     // calculate sizing information
418     float startX = 0;
419     float startY = (alignment & XBFONT_CENTER_Y) ? -0.5f*m_cellHeight : 0;  // vertical centering
420 
421     if ( alignment & (XBFONT_RIGHT | XBFONT_CENTER_X) )
422     {
423       // Get the extent of this line
424       float w = GetTextWidthInternal( text.begin(), text.end() );
425 
426       if ( alignment & XBFONT_TRUNCATED && w > maxPixelWidth + 0.5f ) // + 0.5f due to rounding issues
427         w = maxPixelWidth;
428 
429       if ( alignment & XBFONT_CENTER_X)
430         w *= 0.5f;
431       // Offset this line's starting position
432       startX -= w;
433     }
434 
435     float spacePerSpaceCharacter = 0; // for justification effects
436     if ( alignment & XBFONT_JUSTIFIED )
437     {
438       // first compute the size of the text to render in both characters and pixels
439       unsigned int numSpaces = 0;
440       float linePixels = 0;
441       for (const auto& pos : text)
442       {
443         Character* ch = GetCharacter(pos);
444         if (ch)
445         {
446           if ((pos & 0xffff) == L' ')
447             numSpaces +=  1;
448           linePixels += ch->advance;
449         }
450       }
451       if (numSpaces > 0)
452         spacePerSpaceCharacter = (maxPixelWidth - linePixels) / numSpaces;
453     }
454 
455     float cursorX = 0; // current position along the line
456 
457     // Collect all the Character info in a first pass, in case any of them
458     // are not currently cached and cause the texture to be enlarged, which
459     // would invalidate the texture coordinates.
460     std::queue<Character> characters;
461     if (alignment & XBFONT_TRUNCATED)
462       GetCharacter(L'.');
463     for (const auto& pos : text)
464     {
465       Character* ch = GetCharacter(pos);
466       if (!ch)
467       {
468         Character null = { 0 };
469         characters.push(null);
470         continue;
471       }
472       characters.push(*ch);
473 
474       if (maxPixelWidth > 0 &&
475           cursorX + ((alignment & XBFONT_TRUNCATED) ? ch->advance + 3 * m_ellipsesWidth : 0) > maxPixelWidth)
476         break;
477       cursorX += ch->advance;
478     }
479     cursorX = 0;
480 
481     for (const auto& pos : text)
482     {
483       // If starting text on a new line, determine justification effects
484       // Get the current letter in the CStdString
485       UTILS::Color color = (pos & 0xff0000) >> 16;
486       if (color >= colors.size())
487         color = 0;
488       color = colors[color];
489 
490       // grab the next character
491       Character *ch = &characters.front();
492       if (ch->letterAndStyle == 0)
493       {
494         characters.pop();
495         continue;
496       }
497 
498       if ( alignment & XBFONT_TRUNCATED )
499       {
500         // Check if we will be exceeded the max allowed width
501         if ( cursorX + ch->advance + 3 * m_ellipsesWidth > maxPixelWidth )
502         {
503           // Yup. Let's draw the ellipses, then bail
504           // Perhaps we should really bail to the next line in this case??
505           Character *period = GetCharacter(L'.');
506           if (!period)
507             break;
508 
509           for (int i = 0; i < 3; i++)
510           {
511             RenderCharacter(startX + cursorX, startY, period, color, !scrolling, *tempVertices);
512             cursorX += period->advance;
513           }
514           break;
515         }
516       }
517       else if (maxPixelWidth > 0 && cursorX > maxPixelWidth)
518         break;  // exceeded max allowed width - stop rendering
519 
520       RenderCharacter(startX + cursorX, startY, ch, color, !scrolling, *tempVertices);
521       if ( alignment & XBFONT_JUSTIFIED )
522       {
523         if ((pos & 0xffff) == L' ')
524           cursorX += ch->advance + spacePerSpaceCharacter;
525         else
526           cursorX += ch->advance;
527       }
528       else
529         cursorX += ch->advance;
530       characters.pop();
531     }
532     if (hardwareClipping)
533     {
534       CVertexBuffer &vertexBuffer = m_dynamicCache.Lookup(dynamicPos,
535                                                           colors, text,
536                                                           rawAlignment, maxPixelWidth,
537                                                           scrolling,
538                                                           XbmcThreads::SystemClockMillis(),
539                                                           dirtyCache);
540       CVertexBuffer newVertexBuffer = CreateVertexBuffer(*tempVertices);
541       vertexBuffer = newVertexBuffer;
542       m_vertexTrans.emplace_back(0, 0, 0, &vertexBuffer,
543                                  CServiceBroker::GetWinSystem()->GetGfxContext().GetClipRegion());
544     }
545     else
546     {
547       m_staticCache.Lookup(staticPos,
548                            colors, text,
549                            rawAlignment, maxPixelWidth,
550                            scrolling,
551                            XbmcThreads::SystemClockMillis(),
552                            dirtyCache) = *static_cast<CGUIFontCacheStaticValue *>(&tempVertices);
553       /* Append the new vertices to the set collected since the first Begin() call */
554       m_vertex.insert(m_vertex.end(), tempVertices->begin(), tempVertices->end());
555     }
556   }
557   else
558   {
559     if (hardwareClipping)
560       m_vertexTrans.emplace_back(dynamicPos.m_x, dynamicPos.m_y, dynamicPos.m_z, &vertexBuffer,
561                                  CServiceBroker::GetWinSystem()->GetGfxContext().GetClipRegion());
562     else
563       /* Append the vertices from the cache to the set collected since the first Begin() call */
564       m_vertex.insert(m_vertex.end(), vertices->begin(), vertices->end());
565   }
566 
567   End();
568 }
569 
570 // this routine assumes a single line (i.e. it was called from GUITextLayout)
GetTextWidthInternal(vecText::const_iterator start,vecText::const_iterator end)571 float CGUIFontTTF::GetTextWidthInternal(vecText::const_iterator start, vecText::const_iterator end)
572 {
573   float width = 0;
574   while (start != end)
575   {
576     Character *c = GetCharacter(*start++);
577     if (c)
578     {
579       // If last character in line, we want to add render width
580       // and not advance distance - this makes sure that italic text isn't
581       // choped on the end (as render width is larger than advance then).
582       if (start == end)
583         width += std::max(c->right - c->left + c->offsetX, c->advance);
584       else
585         width += c->advance;
586     }
587   }
588   return width;
589 }
590 
GetCharWidthInternal(character_t ch)591 float CGUIFontTTF::GetCharWidthInternal(character_t ch)
592 {
593   Character *c = GetCharacter(ch);
594   if (c) return c->advance;
595   return 0;
596 }
597 
GetTextHeight(float lineSpacing,int numLines) const598 float CGUIFontTTF::GetTextHeight(float lineSpacing, int numLines) const
599 {
600   return (float)(numLines - 1) * GetLineHeight(lineSpacing) + m_cellHeight;
601 }
602 
GetLineHeight(float lineSpacing) const603 float CGUIFontTTF::GetLineHeight(float lineSpacing) const
604 {
605   if (m_face)
606     return lineSpacing * m_face->size->metrics.height / 64.0f;
607   return 0.0f;
608 }
609 
610 const unsigned int CGUIFontTTF::spacing_between_characters_in_texture = 1;
611 
GetTextureLineHeight() const612 unsigned int CGUIFontTTF::GetTextureLineHeight() const
613 {
614   return m_cellHeight + spacing_between_characters_in_texture;
615 }
616 
GetCharacter(character_t chr)617 CGUIFontTTF::Character* CGUIFontTTF::GetCharacter(character_t chr)
618 {
619   wchar_t letter = (wchar_t)(chr & 0xffff);
620   character_t style = (chr & 0x7000000) >> 24;
621 
622   // ignore linebreaks
623   if (letter == L'\r')
624     return NULL;
625 
626   // quick access to ascii chars
627   if (letter < 255)
628   {
629     character_t ch = (style << 8) | letter;
630     if (ch < LOOKUPTABLE_SIZE && m_charquick[ch])
631       return m_charquick[ch];
632   }
633 
634   // letters are stored based on style and letter
635   character_t ch = (style << 16) | letter;
636 
637   int low = 0;
638   int high = m_numChars - 1;
639   while (low <= high)
640   {
641     int mid = (low + high) >> 1;
642     if (ch > m_char[mid].letterAndStyle)
643       low = mid + 1;
644     else if (ch < m_char[mid].letterAndStyle)
645       high = mid - 1;
646     else
647       return &m_char[mid];
648   }
649   // if we get to here, then low is where we should insert the new character
650 
651   // increase the size of the buffer if we need it
652   if (m_numChars >= m_maxChars)
653   { // need to increase the size of the buffer
654     Character *newTable = new Character[m_maxChars + CHAR_CHUNK];
655     if (m_char)
656     {
657       memcpy(newTable, m_char, low * sizeof(Character));
658       memcpy(newTable + low + 1, m_char + low, (m_numChars - low) * sizeof(Character));
659       delete[] m_char;
660     }
661     m_char = newTable;
662     m_maxChars += CHAR_CHUNK;
663 
664   }
665   else
666   { // just move the data along as necessary
667     memmove(m_char + low + 1, m_char + low, (m_numChars - low) * sizeof(Character));
668   }
669   // render the character to our texture
670   // must End() as we can't render text to our texture during a Begin(), End() block
671   unsigned int nestedBeginCount = m_nestedBeginCount;
672   m_nestedBeginCount = 1;
673   if (nestedBeginCount) End();
674   if (!CacheCharacter(letter, style, m_char + low))
675   { // unable to cache character - try clearing them all out and starting over
676     CLog::Log(LOGDEBUG, "%s: Unable to cache character.  Clearing character cache of %i characters", __FUNCTION__, m_numChars);
677     ClearCharacterCache();
678     low = 0;
679     if (!CacheCharacter(letter, style, m_char + low))
680     {
681       CLog::Log(LOGERROR, "%s: Unable to cache character (out of memory?)", __FUNCTION__);
682       if (nestedBeginCount) Begin();
683       m_nestedBeginCount = nestedBeginCount;
684       return NULL;
685     }
686   }
687   if (nestedBeginCount) Begin();
688   m_nestedBeginCount = nestedBeginCount;
689 
690   // fixup quick access
691   memset(m_charquick, 0, sizeof(m_charquick));
692   for(int i=0;i<m_numChars;i++)
693   {
694     if ((m_char[i].letterAndStyle & 0xffff) < 255)
695     {
696       character_t ch = ((m_char[i].letterAndStyle & 0xffff0000) >> 8) | (m_char[i].letterAndStyle & 0xff);
697       m_charquick[ch] = m_char+i;
698     }
699   }
700 
701   return m_char + low;
702 }
703 
CacheCharacter(wchar_t letter,uint32_t style,Character * ch)704 bool CGUIFontTTF::CacheCharacter(wchar_t letter, uint32_t style, Character* ch)
705 {
706   int glyph_index = FT_Get_Char_Index( m_face, letter );
707 
708   FT_Glyph glyph = NULL;
709   if (FT_Load_Glyph( m_face, glyph_index, FT_LOAD_TARGET_LIGHT ))
710   {
711     CLog::Log(LOGDEBUG, "%s Failed to load glyph %x", __FUNCTION__, static_cast<uint32_t>(letter));
712     return false;
713   }
714   // make bold if applicable
715   if (style & FONT_STYLE_BOLD)
716     SetGlyphStrength(m_face->glyph, GLYPH_STRENGTH_BOLD);
717   // and italics if applicable
718   if (style & FONT_STYLE_ITALICS)
719     ObliqueGlyph(m_face->glyph);
720   // and light if applicable
721   if (style & FONT_STYLE_LIGHT)
722     SetGlyphStrength(m_face->glyph, GLYPH_STRENGTH_LIGHT);
723   // grab the glyph
724   if (FT_Get_Glyph(m_face->glyph, &glyph))
725   {
726     CLog::Log(LOGDEBUG, "%s Failed to get glyph %x", __FUNCTION__, static_cast<uint32_t>(letter));
727     return false;
728   }
729   if (m_stroker)
730     FT_Glyph_StrokeBorder(&glyph, m_stroker, 0, 1);
731   // render the glyph
732   if (FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, NULL, 1))
733   {
734     CLog::Log(LOGDEBUG, "%s Failed to render glyph %x to a bitmap", __FUNCTION__, static_cast<uint32_t>(letter));
735     return false;
736   }
737   FT_BitmapGlyph bitGlyph = (FT_BitmapGlyph)glyph;
738   FT_Bitmap bitmap = bitGlyph->bitmap;
739   bool isEmptyGlyph = (bitmap.width == 0 || bitmap.rows == 0);
740 
741   if (!isEmptyGlyph)
742   {
743     if (bitGlyph->left < 0)
744       m_posX += -bitGlyph->left;
745 
746     // check we have enough room for the character.
747     // cast-fest is here to avoid warnings due to freeetype version differences (signedness of width).
748     if (static_cast<int>(m_posX + bitGlyph->left + bitmap.width) > static_cast<int>(m_textureWidth))
749     { // no space - gotta drop to the next line (which means creating a new texture and copying it across)
750       m_posX = 0;
751       m_posY += GetTextureLineHeight();
752       if (bitGlyph->left < 0)
753         m_posX += -bitGlyph->left;
754 
755       if(m_posY + GetTextureLineHeight() >= m_textureHeight)
756       {
757         // create the new larger texture
758         unsigned int newHeight = m_posY + GetTextureLineHeight();
759         // check for max height
760         if (newHeight > m_renderSystem->GetMaxTextureSize())
761         {
762           CLog::Log(LOGDEBUG, "%s: New cache texture is too large (%u > %u pixels long)", __FUNCTION__, newHeight, m_renderSystem->GetMaxTextureSize());
763           FT_Done_Glyph(glyph);
764           return false;
765         }
766 
767         CTexture* newTexture = NULL;
768         newTexture = ReallocTexture(newHeight);
769         if(newTexture == NULL)
770         {
771           FT_Done_Glyph(glyph);
772           CLog::Log(LOGDEBUG, "%s: Failed to allocate new texture of height %u", __FUNCTION__, newHeight);
773           return false;
774         }
775         m_texture = newTexture;
776       }
777     }
778 
779     if(m_texture == NULL)
780     {
781       FT_Done_Glyph(glyph);
782       CLog::Log(LOGDEBUG, "%s: no texture to cache character to", __FUNCTION__);
783       return false;
784     }
785   }
786   // set the character in our table
787   ch->letterAndStyle = (style << 16) | letter;
788   ch->offsetX = (short)bitGlyph->left;
789   ch->offsetY = (short)m_cellBaseLine - bitGlyph->top;
790   ch->left = isEmptyGlyph ? 0 : ((float)m_posX + ch->offsetX);
791   ch->top = isEmptyGlyph ? 0 : ((float)m_posY + ch->offsetY);
792   ch->right = ch->left + bitmap.width;
793   ch->bottom = ch->top + bitmap.rows;
794   ch->advance = (float)MathUtils::round_int( (float)m_face->glyph->advance.x / 64 );
795 
796   // we need only render if we actually have some pixels
797   if (!isEmptyGlyph)
798   {
799     // ensure our rect will stay inside the texture (it *should* but we need to be certain)
800     unsigned int x1 = std::max(m_posX + ch->offsetX, 0);
801     unsigned int y1 = std::max(m_posY + ch->offsetY, 0);
802     unsigned int x2 = std::min(x1 + bitmap.width, m_textureWidth);
803     unsigned int y2 = std::min(y1 + bitmap.rows, m_textureHeight);
804     CopyCharToTexture(bitGlyph, x1, y1, x2, y2);
805 
806     m_posX += spacing_between_characters_in_texture + (unsigned short)std::max(ch->right - ch->left + ch->offsetX, ch->advance);
807   }
808   m_numChars++;
809 
810   // free the glyph
811   FT_Done_Glyph(glyph);
812 
813   return true;
814 }
815 
RenderCharacter(float posX,float posY,const Character * ch,UTILS::Color color,bool roundX,std::vector<SVertex> & vertices)816 void CGUIFontTTF::RenderCharacter(float posX,
817                                   float posY,
818                                   const Character* ch,
819                                   UTILS::Color color,
820                                   bool roundX,
821                                   std::vector<SVertex>& vertices)
822 {
823   // actual image width isn't same as the character width as that is
824   // just baseline width and height should include the descent
825   const float width = ch->right - ch->left;
826   const float height = ch->bottom - ch->top;
827 
828   // return early if nothing to render
829   if (width == 0 || height == 0)
830     return;
831 
832   // posX and posY are relative to our origin, and the textcell is offset
833   // from our (posX, posY).  Plus, these are unscaled quantities compared to the underlying GUI resolution
834   CRect vertex((posX + ch->offsetX) * CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIScaleX(),
835                (posY + ch->offsetY) * CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIScaleY(),
836                (posX + ch->offsetX + width) * CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIScaleX(),
837                (posY + ch->offsetY + height) * CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIScaleY());
838   vertex += CPoint(m_originX, m_originY);
839   CRect texture(ch->left, ch->top, ch->right, ch->bottom);
840   if (!m_renderSystem->ScissorsCanEffectClipping())
841     CServiceBroker::GetWinSystem()->GetGfxContext().ClipRect(vertex, texture);
842 
843   // transform our positions - note, no scaling due to GUI calibration/resolution occurs
844   float x[4], y[4], z[4];
845 
846   x[0] = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(vertex.x1, vertex.y1);
847   x[1] = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(vertex.x2, vertex.y1);
848   x[2] = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(vertex.x2, vertex.y2);
849   x[3] = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(vertex.x1, vertex.y2);
850 
851   if (roundX)
852   {
853     // We only round the "left" side of the character, and then use the direction of rounding to
854     // move the "right" side of the character.  This ensures that a constant width is kept when rendering
855     // the same letter at the same size at different places of the screen, avoiding the problem
856     // of the "left" side rounding one way while the "right" side rounds the other way, thus getting
857     // altering the width of thin characters substantially.  This only really works for positive
858     // coordinates (due to the direction of truncation for negatives) but this is the only case that
859     // really interests us anyway.
860     float rx0 = (float)MathUtils::round_int(x[0]);
861     float rx3 = (float)MathUtils::round_int(x[3]);
862     x[1] = (float)MathUtils::truncate_int(x[1]);
863     x[2] = (float)MathUtils::truncate_int(x[2]);
864     if (x[0] > 0.0f && rx0 > x[0])
865       x[1] += 1;
866     else if (x[0] < 0.0f && rx0 < x[0])
867       x[1] -= 1;
868     if (x[3] > 0.0f && rx3 > x[3])
869       x[2] += 1;
870     else if (x[3] < 0.0f && rx3 < x[3])
871       x[2] -= 1;
872     x[0] = rx0;
873     x[3] = rx3;
874   }
875 
876   y[0] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(vertex.x1, vertex.y1));
877   y[1] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(vertex.x2, vertex.y1));
878   y[2] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(vertex.x2, vertex.y2));
879   y[3] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(vertex.x1, vertex.y2));
880 
881   z[0] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(vertex.x1, vertex.y1));
882   z[1] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(vertex.x2, vertex.y1));
883   z[2] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(vertex.x2, vertex.y2));
884   z[3] = (float)MathUtils::round_int(CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalZCoord(vertex.x1, vertex.y2));
885 
886   // tex coords converted to 0..1 range
887   float tl = texture.x1 * m_textureScaleX;
888   float tr = texture.x2 * m_textureScaleX;
889   float tt = texture.y1 * m_textureScaleY;
890   float tb = texture.y2 * m_textureScaleY;
891 
892   vertices.resize(vertices.size() + 4);
893   SVertex* v = &vertices[vertices.size() - 4];
894   m_color = color;
895 
896 #if !defined(HAS_DX)
897   unsigned char r = GET_R(color)
898               , g = GET_G(color)
899               , b = GET_B(color)
900               , a = GET_A(color);
901 #endif
902 
903   for(int i = 0; i < 4; i++)
904   {
905 #ifdef HAS_DX
906     CD3DHelper::XMStoreColor(&v[i].col, color);
907 #else
908     v[i].r = r;
909     v[i].g = g;
910     v[i].b = b;
911     v[i].a = a;
912 #endif
913   }
914 
915 #if defined(HAS_DX)
916   for(int i = 0; i < 4; i++)
917   {
918     v[i].x = x[i];
919     v[i].y = y[i];
920     v[i].z = z[i];
921   }
922 
923   v[0].u = tl;
924   v[0].v = tt;
925 
926   v[1].u = tr;
927   v[1].v = tt;
928 
929   v[2].u = tr;
930   v[2].v = tb;
931 
932   v[3].u = tl;
933   v[3].v = tb;
934 #else
935   // GL / GLES uses triangle strips, not quads, so have to rearrange the vertex order
936   v[0].u = tl;
937   v[0].v = tt;
938   v[0].x = x[0];
939   v[0].y = y[0];
940   v[0].z = z[0];
941 
942   v[1].u = tl;
943   v[1].v = tb;
944   v[1].x = x[3];
945   v[1].y = y[3];
946   v[1].z = z[3];
947 
948   v[2].u = tr;
949   v[2].v = tt;
950   v[2].x = x[1];
951   v[2].y = y[1];
952   v[2].z = z[1];
953 
954   v[3].u = tr;
955   v[3].v = tb;
956   v[3].x = x[2];
957   v[3].y = y[2];
958   v[3].z = z[2];
959 #endif
960 }
961 
962 // Oblique code - original taken from freetype2 (ftsynth.c)
ObliqueGlyph(FT_GlyphSlot slot)963 void CGUIFontTTF::ObliqueGlyph(FT_GlyphSlot slot)
964 {
965   /* only oblique outline glyphs */
966   if ( slot->format != FT_GLYPH_FORMAT_OUTLINE )
967     return;
968 
969   /* we don't touch the advance width */
970 
971   /* For italic, simply apply a shear transform, with an angle */
972   /* of about 12 degrees.                                      */
973 
974   FT_Matrix    transform;
975   transform.xx = 0x10000L;
976   transform.yx = 0x00000L;
977 
978   transform.xy = 0x06000L;
979   transform.yy = 0x10000L;
980 
981   FT_Outline_Transform( &slot->outline, &transform );
982 }
983 
984 
985 // Embolden code - original taken from freetype2 (ftsynth.c)
SetGlyphStrength(FT_GlyphSlot slot,int glyphStrength)986 void CGUIFontTTF::SetGlyphStrength(FT_GlyphSlot slot, int glyphStrength)
987 {
988   if ( slot->format != FT_GLYPH_FORMAT_OUTLINE )
989     return;
990 
991   /* some reasonable strength */
992   FT_Pos strength = FT_MulFix( m_face->units_per_EM,
993                     m_face->size->metrics.y_scale ) / glyphStrength;
994 
995   FT_BBox bbox_before, bbox_after;
996   FT_Outline_Get_CBox( &slot->outline, &bbox_before );
997   FT_Outline_Embolden( &slot->outline, strength );  // ignore error
998   FT_Outline_Get_CBox( &slot->outline, &bbox_after );
999 
1000   FT_Pos dx = bbox_after.xMax - bbox_before.xMax;
1001   FT_Pos dy = bbox_after.yMax - bbox_before.yMax;
1002 
1003   if ( slot->advance.x )
1004     slot->advance.x += dx;
1005 
1006   if ( slot->advance.y )
1007     slot->advance.y += dy;
1008 
1009   slot->metrics.width        += dx;
1010   slot->metrics.height       += dy;
1011   slot->metrics.horiBearingY += dy;
1012   slot->metrics.horiAdvance  += dx;
1013   slot->metrics.vertBearingX -= dx / 2;
1014   slot->metrics.vertBearingY += dy;
1015   slot->metrics.vertAdvance  += dy;
1016 }
1017