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