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