2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
22 #include "GUIFontTTF.h"
23 #include "GUIFontManager.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"
35 #include FT_FREETYPE_H
40 #define USE_RELEASE_LIBS
43 #pragma comment(lib, "freetype246MT.lib")
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)
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.
56 class CFreeTypeLibrary
64 virtual ~CFreeTypeLibrary()
67 FT_Done_FreeType(m_library);
70 FT_Face GetFont(const CStdString &filename, float size, float aspect)
72 // don't have it yet - create it
74 FT_Init_FreeType(&m_library);
77 CLog::Log(LOGERROR, "Unable to initialize freetype library");
83 // ok, now load the font face
84 if (FT_New_Face( m_library, CSpecialProtocol::TranslatePath(filename).c_str(), 0, &face ))
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);
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 ))
103 FT_Stroker GetStroker()
109 if (FT_Stroker_New(m_library, &stroker))
115 void ReleaseFont(FT_Face face)
121 void ReleaseStroker(FT_Stroker stroker)
124 FT_Stroker_Done(stroker);
128 FT_Library m_library;
131 XBMC_GLOBAL_REF(CFreeTypeLibrary, g_freeTypeLibrary); // our freetype library
132 #define g_freeTypeLibrary XBMC_GLOBAL_USE(CFreeTypeLibrary)
134 CGUIFontTTFBase::CGUIFontTTFBase(const CStdString& strFileName)
139 m_nestedBeginCount = 0;
141 m_bTextureLoaded = false;
142 m_vertex_size = 4*1024;
143 m_vertex = (SVertex*)malloc(m_vertex_size * sizeof(SVertex));
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;
154 m_textureHeight = m_textureWidth = 0;
155 m_textureScaleX = m_textureScaleY = 0.0;
156 m_ellipsesWidth = m_height = 0.0f;
162 CGUIFontTTFBase::~CGUIFontTTFBase(void)
167 void CGUIFontTTFBase::AddReference()
172 void CGUIFontTTFBase::RemoveReference()
174 // delete this object when it's reference count hits zero
176 if (!m_referenceCount)
177 g_fontManager.FreeFontFile(this);
181 void CGUIFontTTFBase::ClearCharacterCache()
185 DeleteHardwareTexture();
189 m_char = new Character[CHAR_CHUNK];
190 memset(m_charquick, 0, sizeof(m_charquick));
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();
199 void CGUIFontTTFBase::Clear()
204 memset(m_charquick, 0, sizeof(m_charquick));
210 m_nestedBeginCount = 0;
213 g_freeTypeLibrary.ReleaseFont(m_face);
216 g_freeTypeLibrary.ReleaseStroker(m_stroker);
224 bool CGUIFontTTFBase::Load(const CStdString& strFilename, float height, float aspect, float lineSpacing, bool border)
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);
234 the values used are described below
236 XBMC coords Freetype coords
238 0 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ bbox.yMax, ascender
242 AAAAA pppp cellAscender
245 m_cellBaseLine _ _A_ _A_ pppp_ _ _ _ _/_ _ _ _ _ 0, base line.
248 m_cellHeight _ _ _ _ _ p _ _ _ _ _ _/_ _ _ _ _ bbox.yMin, descender
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);
257 add on the strength of any border - the non-bordered font needs
258 aligning with the bordered font by utilising GetTextBaseLine()
260 FT_Pos strength = FT_MulFix( m_face->units_per_EM, m_face->size->metrics.y_scale) / 12;
264 cellDescender -= strength;
265 cellAscender += strength;
267 m_stroker = g_freeTypeLibrary.GetStroker();
269 FT_Stroker_Set(m_stroker, strength, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
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
277 m_cellBaseLine = cellAscender;
278 m_cellHeight = cellAscender - cellDescender;
290 m_strFilename = strFilename;
293 m_textureWidth = ((m_cellHeight * CHARS_PER_TEXTURE_LINE) & ~63) + 64;
295 m_textureWidth = CBaseTexture::PadPow2(m_textureWidth);
297 if (m_textureWidth > g_Windowing.GetMaxTextureSize())
298 m_textureWidth = g_Windowing.GetMaxTextureSize();
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();
304 // cache the ellipses width
305 Character *ellipse = GetCharacter(L'.');
306 if (ellipse) m_ellipsesWidth = ellipse->advance;
311 void CGUIFontTTFBase::DrawTextInternal(float x, float y, const vecColors &colors, const vecText &text, uint32_t alignment, float maxPixelWidth, bool scrolling)
315 // save the origin, which is scaled separately
319 // Check if we will really need to truncate or justify the text
320 if ( alignment & XBFONT_TRUNCATED )
322 if ( maxPixelWidth <= 0.0f || GetTextWidthInternal(text.begin(), text.end()) <= maxPixelWidth)
323 alignment &= ~XBFONT_TRUNCATED;
325 else if ( alignment & XBFONT_JUSTIFIED )
327 if ( maxPixelWidth <= 0.0f )
328 alignment &= ~XBFONT_JUSTIFIED;
331 // calculate sizing information
333 float startY = (alignment & XBFONT_CENTER_Y) ? -0.5f*m_cellHeight : 0; // vertical centering
335 if ( alignment & (XBFONT_RIGHT | XBFONT_CENTER_X) )
337 // Get the extent of this line
338 float w = GetTextWidthInternal( text.begin(), text.end() );
340 if ( alignment & XBFONT_TRUNCATED && w > maxPixelWidth + 0.5f ) // + 0.5f due to rounding issues
343 if ( alignment & XBFONT_CENTER_X)
345 // Offset this line's starting position
349 float spacePerLetter = 0; // for justification effects
350 if ( alignment & XBFONT_JUSTIFIED )
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++)
357 Character *ch = GetCharacter(*pos);
359 { // spaces have multiple times the justification spacing of normal letters
360 lineChars += ((*pos & 0xffff) == L' ') ? justification_word_weight : 1;
361 linePixels += ch->advance;
365 spacePerLetter = (maxPixelWidth - linePixels) / (lineChars - 1);
367 float cursorX = 0; // current position along the line
369 for (vecText::const_iterator pos = text.begin(); pos != text.end(); pos++)
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())
376 color = colors[color];
378 // grab the next character
379 Character *ch = GetCharacter(*pos);
382 if ( alignment & XBFONT_TRUNCATED )
384 // Check if we will be exceeded the max allowed width
385 if ( cursorX + ch->advance + 3 * m_ellipsesWidth > maxPixelWidth )
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'.');
393 for (int i = 0; i < 3; i++)
395 RenderCharacter(startX + cursorX, startY, period, color, !scrolling);
396 cursorX += period->advance;
401 else if (maxPixelWidth > 0 && cursorX > maxPixelWidth)
402 break; // exceeded max allowed width - stop rendering
404 RenderCharacter(startX + cursorX, startY, ch, color, !scrolling);
405 if ( alignment & XBFONT_JUSTIFIED )
407 if ((*pos & 0xffff) == L' ')
408 cursorX += ch->advance + spacePerLetter * justification_word_weight;
410 cursorX += ch->advance + spacePerLetter;
413 cursorX += ch->advance;
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)
425 Character *c = GetCharacter(*start++);
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).
432 width += max(c->right - c->left + c->offsetX, c->advance);
440 float CGUIFontTTFBase::GetCharWidthInternal(character_t ch)
442 Character *c = GetCharacter(ch);
443 if (c) return c->advance;
447 float CGUIFontTTFBase::GetTextHeight(float lineSpacing, int numLines) const
449 return (float)(numLines - 1) * GetLineHeight(lineSpacing) + m_cellHeight;
452 float CGUIFontTTFBase::GetLineHeight(float lineSpacing) const
455 return lineSpacing * m_face->size->metrics.height / 64.0f;
459 unsigned int CGUIFontTTFBase::spacing_between_characters_in_texture = 1;
461 unsigned int CGUIFontTTFBase::GetTextureLineHeight() const
463 return m_cellHeight + spacing_between_characters_in_texture;
466 CGUIFontTTFBase::Character* CGUIFontTTFBase::GetCharacter(character_t chr)
468 wchar_t letter = (wchar_t)(chr & 0xffff);
469 character_t style = (chr & 0x3000000) >> 24;
475 // quick access to ascii chars
478 character_t ch = (style << 8) | letter;
480 return m_charquick[ch];
483 // letters are stored based on style and letter
484 character_t ch = (style << 16) | letter;
487 int high = m_numChars - 1;
491 mid = (low + high) >> 1;
492 if (ch > m_char[mid].letterAndStyle)
494 else if (ch < m_char[mid].letterAndStyle)
499 // if we get to here, then low is where we should insert the new character
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];
507 memcpy(newTable, m_char, low * sizeof(Character));
508 memcpy(newTable + low + 1, m_char + low, (m_numChars - low) * sizeof(Character));
512 m_maxChars += CHAR_CHUNK;
516 { // just move the data along as necessary
517 memmove(m_char + low + 1, m_char + low, (m_numChars - low) * sizeof(Character));
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();
529 if (!CacheCharacter(letter, style, m_char + low))
531 CLog::Log(LOGERROR, "GUIFontTTF::GetCharacter: Unable to cache character (out of memory?)");
532 if (nestedBeginCount) Begin();
533 m_nestedBeginCount = nestedBeginCount;
537 if (nestedBeginCount) Begin();
538 m_nestedBeginCount = nestedBeginCount;
540 // fixup quick access
541 memset(m_charquick, 0, sizeof(m_charquick));
542 for(int i=0;i<m_numChars;i++)
544 if ((m_char[i].letterAndStyle & 0xffff) < 255)
546 character_t ch = ((m_char[i].letterAndStyle & 0xffff0000) >> 8) | (m_char[i].letterAndStyle & 0xff);
547 m_charquick[ch] = m_char+i;
554 bool CGUIFontTTFBase::CacheCharacter(wchar_t letter, uint32_t style, Character *ch)
556 int glyph_index = FT_Get_Char_Index( m_face, letter );
558 FT_Glyph glyph = NULL;
559 if (FT_Load_Glyph( m_face, glyph_index, FT_LOAD_TARGET_LIGHT ))
561 CLog::Log(LOGDEBUG, "%s Failed to load glyph %x", __FUNCTION__, letter);
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);
571 if (FT_Get_Glyph(m_face->glyph, &glyph))
573 CLog::Log(LOGDEBUG, "%s Failed to get glyph %x", __FUNCTION__, letter);
577 FT_Glyph_StrokeBorder(&glyph, m_stroker, 0, 1);
579 if (FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, NULL, 1))
581 CLog::Log(LOGDEBUG, "%s Failed to render glyph %x to a bitmap", __FUNCTION__, letter);
584 FT_BitmapGlyph bitGlyph = (FT_BitmapGlyph)glyph;
585 FT_Bitmap bitmap = bitGlyph->bitmap;
586 if (bitGlyph->left < 0)
587 m_posX += -bitGlyph->left;
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)
593 m_posY += GetTextureLineHeight();
594 if (bitGlyph->left < 0)
595 m_posX += -bitGlyph->left;
597 if(m_posY + GetTextureLineHeight() >= m_textureHeight)
599 // create the new larger texture
600 unsigned int newHeight = m_posY + GetTextureLineHeight();
601 // check for max height
602 if (newHeight > g_Windowing.GetMaxTextureSize())
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);
609 CBaseTexture* newTexture = NULL;
610 newTexture = ReallocTexture(newHeight);
611 if(newTexture == NULL)
613 FT_Done_Glyph(glyph);
614 CLog::Log(LOGDEBUG, "GUIFontTTF::CacheCharacter: Failed to allocate new texture of height %u", newHeight);
617 m_texture = newTexture;
621 if(m_texture == NULL)
623 CLog::Log(LOGDEBUG, "GUIFontTTF::CacheCharacter: no texture to cache character to");
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 );
637 // we need only render if we actually have some pixels
638 if (bitmap.width * bitmap.rows)
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);
647 m_posX += spacing_between_characters_in_texture + (unsigned short)max(ch->right - ch->left + ch->offsetX, ch->advance);
650 m_textureScaleX = 1.0f / m_textureWidth;
651 m_textureScaleY = 1.0f / m_textureHeight;
654 FT_Done_Glyph(glyph);
659 void CGUIFontTTFBase::RenderCharacter(float posX, float posY, const Character *ch, color_t color, bool roundX)
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;
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);
676 // transform our positions - note, no scaling due to GUI calibration/resolution occurs
677 float x[4], y[4], z[4];
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);
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]);
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));
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));
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;
721 // grow the vertex buffer if required
722 if(m_vertex_count >= m_vertex_size)
725 void* old = m_vertex;
726 m_vertex = (SVertex*)realloc(m_vertex, m_vertex_size * sizeof(SVertex));
730 printf("realloc failed in CGUIFontTTF::RenderCharacter. aborting\n");
736 SVertex* v = m_vertex + m_vertex_count;
738 for(int i = 0; i < 4; i++)
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);
746 #if defined(HAS_GL) || defined(HAS_DX)
747 for(int i = 0; i < 4; i++)
766 // GLES uses triangle strips, not quads, so have to rearrange the vertex order
795 // Oblique code - original taken from freetype2 (ftsynth.c)
796 void CGUIFontTTFBase::ObliqueGlyph(FT_GlyphSlot slot)
798 /* only oblique outline glyphs */
799 if ( slot->format != FT_GLYPH_FORMAT_OUTLINE )
802 /* we don't touch the advance width */
804 /* For italic, simply apply a shear transform, with an angle */
805 /* of about 12 degrees. */
808 transform.xx = 0x10000L;
809 transform.yx = 0x00000L;
811 transform.xy = 0x06000L;
812 transform.yy = 0x10000L;
814 FT_Outline_Transform( &slot->outline, &transform );
818 // Embolden code - original taken from freetype2 (ftsynth.c)
819 void CGUIFontTTFBase::EmboldenGlyph(FT_GlyphSlot slot)
821 if ( slot->format != FT_GLYPH_FORMAT_OUTLINE )
824 /* some reasonable strength */
825 FT_Pos strength = FT_MulFix( m_face->units_per_EM,
826 m_face->size->metrics.y_scale ) / 24;
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 );
833 FT_Pos dx = bbox_after.xMax - bbox_before.xMax;
834 FT_Pos dy = bbox_after.yMax - bbox_before.yMax;
836 if ( slot->advance.x )
837 slot->advance.x += dx;
839 if ( slot->advance.y )
840 slot->advance.y += dy;
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;