PageRenderTime 91ms CodeModel.GetById 17ms app.highlight 66ms RepoModel.GetById 1ms app.codeStats 0ms

/xbmc/guilib/GUIFontTTF.cpp

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