[Test] Added tests for CUtil::SplitParams
[xbmc.git] / xbmc / guilib / GUIFontTTF.cpp
blob10041f7d61ec817fc567f537d57b395b1460dee1
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "GUIFontTTF.h"
11 #include "GUIFontManager.h"
12 #include "ServiceBroker.h"
13 #include "Texture.h"
14 #include "URL.h"
15 #include "filesystem/File.h"
16 #include "filesystem/SpecialProtocol.h"
17 #include "rendering/RenderSystem.h"
18 #include "threads/SystemClock.h"
19 #include "utils/MathUtils.h"
20 #include "utils/log.h"
21 #include "windowing/GraphicContext.h"
22 #include "windowing/WinSystem.h"
24 #include <math.h>
25 #include <memory>
26 #include <queue>
27 #include <utility>
29 // stuff for freetype
30 #include <ft2build.h>
31 #include <harfbuzz/hb-ft.h>
32 #if defined(HAS_GL) || defined(HAS_GLES)
33 #include "utils/GLUtils.h"
35 #include "system_gl.h"
36 #endif
38 #if defined(HAS_DX)
39 #include "guilib/D3DResource.h"
40 #endif
42 #ifdef TARGET_WINDOWS_STORE
43 #define generic GenericFromFreeTypeLibrary
44 #endif
46 #include FT_FREETYPE_H
47 #include FT_GLYPH_H
48 #include FT_OUTLINE_H
49 #include FT_STROKER_H
51 namespace
53 constexpr int VERTEX_PER_GLYPH = 4; // number of vertex for each glyph
54 constexpr int CHARS_PER_TEXTURE_LINE = 20; // number characters to cache per texture line
55 constexpr int MAX_TRANSLATED_VERTEX = 32; // max number of structs CTranslatedVertices expect to use
56 constexpr int MAX_GLYPHS_PER_TEXT_LINE = 1024; // max number of glyphs per text line expect to use
57 constexpr unsigned int SPACING_BETWEEN_CHARACTERS_IN_TEXTURE = 1;
58 constexpr int CHAR_CHUNK = 64; // 64 chars allocated at a time (2048 bytes)
59 constexpr int GLYPH_STRENGTH_BOLD = 24;
60 constexpr int GLYPH_STRENGTH_LIGHT = -48;
61 constexpr int TAB_SPACE_LENGTH = 4;
62 } /* namespace */
64 class CFreeTypeLibrary
66 public:
67 CFreeTypeLibrary() = default;
68 virtual ~CFreeTypeLibrary()
70 if (m_library)
71 FT_Done_FreeType(m_library);
74 FT_Face GetFont(const std::string& filename,
75 float size,
76 float aspect,
77 std::vector<uint8_t>& memoryBuf)
79 // don't have it yet - create it
80 if (!m_library)
81 FT_Init_FreeType(&m_library);
82 if (!m_library)
84 CLog::LogF(LOGERROR, "Unable to initialize freetype library");
85 return nullptr;
88 FT_Face face;
90 // ok, now load the font face
91 CURL realFile(CSpecialProtocol::TranslatePath(filename));
92 if (realFile.GetFileName().empty())
93 return nullptr;
95 memoryBuf.clear();
96 #ifndef TARGET_WINDOWS
97 if (!realFile.GetProtocol().empty())
98 #endif // ! TARGET_WINDOWS
100 // load file into memory if it is not on local drive
101 // in case of win32: always load file into memory as filename is in UTF-8,
102 // but freetype expect filename in ANSI encoding
103 XFILE::CFile f;
104 if (f.LoadFile(realFile, memoryBuf) <= 0)
105 return nullptr;
107 if (FT_New_Memory_Face(m_library, reinterpret_cast<const FT_Byte*>(memoryBuf.data()),
108 memoryBuf.size(), 0, &face) != 0)
109 return nullptr;
111 #ifndef TARGET_WINDOWS
112 else if (FT_New_Face(m_library, realFile.GetFileName().c_str(), 0, &face))
113 return nullptr;
114 #endif // ! TARGET_WINDOWS
116 unsigned int ydpi = 72; // 72 points to the inch is the freetype default
117 unsigned int xdpi =
118 static_cast<unsigned int>(MathUtils::round_int(static_cast<double>(ydpi * aspect)));
120 // we set our screen res currently to 96dpi in both directions (windows default)
121 // we cache our characters (for rendering speed) so it's probably
122 // not a good idea to allow free scaling of fonts - rather, just
123 // scaling to pixel ratio on screen perhaps?
124 if (FT_Set_Char_Size(face, 0, static_cast<int>(size * 64 + 0.5f), xdpi, ydpi))
126 FT_Done_Face(face);
127 return nullptr;
130 return face;
133 FT_Stroker GetStroker()
135 if (!m_library)
136 return nullptr;
138 FT_Stroker stroker;
139 if (FT_Stroker_New(m_library, &stroker))
140 return nullptr;
142 return stroker;
145 static void ReleaseFont(FT_Face face)
147 assert(face);
148 FT_Done_Face(face);
151 static void ReleaseStroker(FT_Stroker stroker)
153 assert(stroker);
154 FT_Stroker_Done(stroker);
157 private:
158 FT_Library m_library{nullptr};
161 XBMC_GLOBAL_REF(CFreeTypeLibrary, g_freeTypeLibrary); // our freetype library
162 #define g_freeTypeLibrary XBMC_GLOBAL_USE(CFreeTypeLibrary)
164 CGUIFontTTF::CGUIFontTTF(const std::string& fontIdent)
165 : m_fontIdent(fontIdent),
166 m_staticCache(*this),
167 m_dynamicCache(*this),
168 m_renderSystem(CServiceBroker::GetRenderSystem())
172 CGUIFontTTF::~CGUIFontTTF(void)
174 Clear();
177 void CGUIFontTTF::AddReference()
179 m_referenceCount++;
182 void CGUIFontTTF::RemoveReference()
184 // delete this object when it's reference count hits zero
185 m_referenceCount--;
186 if (!m_referenceCount)
187 g_fontManager.FreeFontFile(this);
191 void CGUIFontTTF::ClearCharacterCache()
193 m_texture.reset();
195 DeleteHardwareTexture();
197 m_texture = nullptr;
198 m_char.clear();
199 m_char.reserve(CHAR_CHUNK);
200 memset(m_charquick, 0, sizeof(m_charquick));
201 // set the posX and posY so that our texture will be created on first character write.
202 m_posX = m_textureWidth;
203 m_posY = -static_cast<int>(GetTextureLineHeight());
204 m_textureHeight = 0;
207 void CGUIFontTTF::Clear()
209 m_texture.reset();
210 m_texture = nullptr;
211 memset(m_charquick, 0, sizeof(m_charquick));
212 m_posX = 0;
213 m_posY = 0;
214 m_nestedBeginCount = 0;
216 if (m_hbFont)
217 hb_font_destroy(m_hbFont);
218 m_hbFont = nullptr;
219 if (m_face)
220 g_freeTypeLibrary.ReleaseFont(m_face);
221 m_face = nullptr;
222 if (m_stroker)
223 g_freeTypeLibrary.ReleaseStroker(m_stroker);
224 m_stroker = nullptr;
226 m_vertexTrans.clear();
227 m_vertex.clear();
229 m_fontFileInMemory.clear();
232 bool CGUIFontTTF::Load(
233 const std::string& strFilename, float height, float aspect, float lineSpacing, bool border)
235 // we now know that this object is unique - only the GUIFont objects are non-unique, so no need
236 // for reference tracking these fonts
237 m_face = g_freeTypeLibrary.GetFont(strFilename, height, aspect, m_fontFileInMemory);
238 if (!m_face)
239 return false;
241 m_hbFont = hb_ft_font_create(m_face, 0);
242 if (!m_hbFont)
243 return false;
245 the values used are described below
247 XBMC coords Freetype coords
249 0 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ bbox.yMax, ascender
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.
258 p cellDescender
259 m_cellHeight _ _ _ _ _ p _ _ _ _ _ _/_ _ _ _ _ bbox.yMin, descender
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);
265 if (border)
268 add on the strength of any border - the non-bordered font needs
269 aligning with the bordered font by utilising GetTextBaseLine()
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;
275 cellDescender -= strength;
276 cellAscender += strength;
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);
283 // scale to pixel sizing, rounding so that maximal extent is obtained
284 float scaler = height / m_face->units_per_EM;
285 cellDescender =
286 MathUtils::round_int(cellDescender * static_cast<double>(scaler) - 0.5); // round down
287 cellAscender = MathUtils::round_int(cellAscender * static_cast<double>(scaler) + 0.5); // round up
289 m_cellBaseLine = cellAscender;
290 m_cellHeight = cellAscender - cellDescender;
292 m_height = height;
294 m_texture.reset();
295 m_texture = nullptr;
297 m_textureHeight = 0;
298 m_textureWidth = ((m_cellHeight * CHARS_PER_TEXTURE_LINE) & ~63) + 64;
300 m_textureWidth = CTexture::PadPow2(m_textureWidth);
302 if (m_textureWidth > m_renderSystem->GetMaxTextureSize())
303 m_textureWidth = m_renderSystem->GetMaxTextureSize();
304 m_textureScaleX = 1.0f / m_textureWidth;
306 // set the posX and posY so that our texture will be created on first character write.
307 m_posX = m_textureWidth;
308 m_posY = -static_cast<int>(GetTextureLineHeight());
310 return true;
313 void CGUIFontTTF::Begin()
315 if (m_nestedBeginCount == 0 && m_texture && FirstBegin())
317 m_vertexTrans.clear();
318 m_vertex.clear();
320 // Keep track of the nested begin/end calls.
321 m_nestedBeginCount++;
324 void CGUIFontTTF::End()
326 if (m_nestedBeginCount == 0)
327 return;
329 if (--m_nestedBeginCount > 0)
330 return;
332 LastEnd();
335 void CGUIFontTTF::DrawTextInternal(CGraphicContext& context,
336 float x,
337 float y,
338 const std::vector<UTILS::COLOR::Color>& colors,
339 const vecText& text,
340 uint32_t alignment,
341 float maxPixelWidth,
342 bool scrolling)
344 if (text.empty())
346 return;
349 Begin();
350 uint32_t rawAlignment = alignment;
351 bool dirtyCache(false);
352 const bool hardwareClipping = m_renderSystem->ScissorsCanEffectClipping();
353 CGUIFontCacheStaticPosition staticPos(x, y);
354 CGUIFontCacheDynamicPosition dynamicPos;
355 if (hardwareClipping)
357 dynamicPos =
358 CGUIFontCacheDynamicPosition(context.ScaleFinalXCoord(x, y), context.ScaleFinalYCoord(x, y),
359 context.ScaleFinalZCoord(x, y));
362 CVertexBuffer unusedVertexBuffer;
363 CVertexBuffer& vertexBuffer =
364 hardwareClipping
365 ? m_dynamicCache.Lookup(context, dynamicPos, colors, text, alignment, maxPixelWidth,
366 scrolling, std::chrono::steady_clock::now(), dirtyCache)
367 : unusedVertexBuffer;
368 std::shared_ptr<std::vector<SVertex>> tempVertices = std::make_shared<std::vector<SVertex>>();
369 std::shared_ptr<std::vector<SVertex>>& vertices =
370 hardwareClipping ? tempVertices
371 : static_cast<std::shared_ptr<std::vector<SVertex>>&>(m_staticCache.Lookup(
372 context, staticPos, colors, text, alignment, maxPixelWidth, scrolling,
373 std::chrono::steady_clock::now(), dirtyCache));
375 // reserves vertex vector capacity, only the ones that are going to be used
376 if (hardwareClipping)
378 if (m_vertexTrans.capacity() == 0)
379 m_vertexTrans.reserve(MAX_TRANSLATED_VERTEX);
381 else
383 if (m_vertex.capacity() == 0)
384 m_vertex.reserve(VERTEX_PER_GLYPH * MAX_GLYPHS_PER_TEXT_LINE);
387 if (dirtyCache)
389 const std::vector<Glyph> glyphs = GetHarfBuzzShapedGlyphs(text);
390 // save the origin, which is scaled separately
391 m_originX = x;
392 m_originY = y;
394 // cache the ellipses width
395 if (!m_ellipseCached)
397 m_ellipseCached = true;
398 Character* ellipse = GetCharacter(L'.', 0);
399 if (ellipse)
400 m_ellipsesWidth = ellipse->m_advance;
403 // Define the width of ellipses of three chars "..."
404 const float ellipsesWidth = 3 * m_ellipsesWidth;
406 // Check if we will really need to truncate or justify the text
407 if (alignment & XBFONT_TRUNCATED)
409 if (maxPixelWidth <= 0.0f || GetTextWidthInternal(text, glyphs) <= maxPixelWidth)
410 alignment &= ~XBFONT_TRUNCATED;
412 else if (alignment & XBFONT_JUSTIFIED)
414 if (maxPixelWidth <= 0.0f)
415 alignment &= ~XBFONT_JUSTIFIED;
418 // calculate sizing information
419 float startX = 0;
420 float startY = (alignment & XBFONT_CENTER_Y) ? -0.5f * m_cellHeight : 0; // vertical centering
422 // Defines whether ellipses are to be added at the beginning for right-aligned text
423 bool isBeginWithEllipses{false};
424 // Defines the index position where start rendering glyphs
425 size_t startPosGlyph{0};
427 if (alignment & XBFONT_CENTER_X)
429 // Get the extent of this line
430 float w = GetTextWidthInternal(text, glyphs);
432 if (alignment & XBFONT_TRUNCATED && w > maxPixelWidth + 0.5f) // + 0.5f due to rounding issues
433 w = maxPixelWidth;
435 if (alignment & XBFONT_CENTER_X)
436 w *= 0.5f;
437 // Offset this line's starting position
438 startX -= w;
440 else if (alignment & XBFONT_RIGHT)
442 // We need to determine the point at which to stop rendering glyphs starting from the left,
443 // if "truncated" flag is set then we need to take in account ellipses before the text
444 float textWidth{0};
445 if (alignment & XBFONT_TRUNCATED)
447 textWidth = ellipsesWidth;
448 isBeginWithEllipses = true;
450 // We need to iterate from the end to the beginning
451 for (auto itRGlyph = glyphs.crbegin(); itRGlyph != glyphs.crend(); ++itRGlyph)
453 Character* ch =
454 GetCharacter(text[itRGlyph->m_glyphInfo.cluster], itRGlyph->m_glyphInfo.codepoint);
455 if (!ch)
456 continue;
458 textWidth += ch->m_advance;
460 if (textWidth > maxPixelWidth)
462 // Start rendering from the glyph that does not exceed the maximum width
463 startPosGlyph = std::distance(itRGlyph, glyphs.crend());
464 break;
469 float spacePerSpaceCharacter = 0; // for justification effects
470 if (alignment & XBFONT_JUSTIFIED)
472 // first compute the size of the text to render in both characters and pixels
473 unsigned int numSpaces = 0;
474 float linePixels = 0;
475 for (const auto& glyph : glyphs)
477 Character* ch = GetCharacter(text[glyph.m_glyphInfo.cluster], glyph.m_glyphInfo.codepoint);
478 if (ch)
480 if ((text[glyph.m_glyphInfo.cluster] & 0xffff) == L' ')
481 numSpaces += 1;
482 linePixels += ch->m_advance;
485 if (numSpaces > 0)
486 spacePerSpaceCharacter = (maxPixelWidth - linePixels) / numSpaces;
489 float cursorX = 0; // current position along the line
490 float offsetX = 0;
491 float offsetY = 0;
493 // Collect all the Character info in a first pass, in case any of them
494 // are not currently cached and cause the texture to be enlarged, which
495 // would invalidate the texture coordinates.
496 std::queue<Character> characters;
497 if (alignment & XBFONT_TRUNCATED)
498 GetCharacter(L'.', 0);
500 if (isBeginWithEllipses) // for right aligned text only
501 cursorX += ellipsesWidth;
503 for (auto itGlyph = glyphs.cbegin() + startPosGlyph; itGlyph != glyphs.cend(); ++itGlyph)
505 Character* ch =
506 GetCharacter(text[itGlyph->m_glyphInfo.cluster], itGlyph->m_glyphInfo.codepoint);
507 if (!ch)
509 Character null = {};
510 characters.push(null);
511 continue;
513 characters.push(*ch);
515 if (maxPixelWidth > 0)
517 float nextCursorX = cursorX;
519 if (alignment & XBFONT_TRUNCATED)
521 nextCursorX += ch->m_advance;
522 if (!isBeginWithEllipses) // for left aligned text only
523 nextCursorX += ellipsesWidth;
525 if (nextCursorX > maxPixelWidth)
526 break;
529 cursorX += ch->m_advance;
532 // Reserve vector space: 4 vertex for each glyph
533 tempVertices->reserve(VERTEX_PER_GLYPH * glyphs.size());
534 cursorX = 0;
536 for (auto itGlyph = glyphs.cbegin() + startPosGlyph; itGlyph != glyphs.cend(); ++itGlyph)
538 // If starting text on a new line, determine justification effects
539 // Get the current letter in the CStdString
540 UTILS::COLOR::Color color = (text[itGlyph->m_glyphInfo.cluster] & 0xff0000) >> 16;
541 if (color >= colors.size())
542 color = 0;
543 color = colors[color];
545 // grab the next character
546 Character* ch = &characters.front();
548 if ((text[itGlyph->m_glyphInfo.cluster] & 0xffff) == static_cast<character_t>('\t'))
550 const float tabwidth = GetTabSpaceLength();
551 const float a = cursorX / tabwidth;
552 cursorX += tabwidth - ((a - floorf(a)) * tabwidth);
553 characters.pop();
554 continue;
557 if (alignment & XBFONT_TRUNCATED)
559 if (alignment & XBFONT_RIGHT)
561 if (isBeginWithEllipses)
563 isBeginWithEllipses = false;
564 Character* period = GetCharacter(L'.', 0);
565 if (!period)
566 break;
568 for (int i = 0; i < 3; i++)
570 RenderCharacter(context, startX + cursorX, startY, period, color, !scrolling,
571 *tempVertices);
572 cursorX += period->m_advance;
575 if (maxPixelWidth > 0 && cursorX > maxPixelWidth)
576 break; // exceeded max allowed width - stop rendering
578 else
580 // Check if we will be exceeded the max allowed width
581 if (cursorX + ch->m_advance + ellipsesWidth > maxPixelWidth)
583 // Yup. Let's draw the ellipses, then bail
584 // Perhaps we should really bail to the next line in this case??
585 Character* period = GetCharacter(L'.', 0);
586 if (!period)
587 break;
589 for (int i = 0; i < 3; i++)
591 RenderCharacter(context, startX + cursorX, startY, period, color, !scrolling,
592 *tempVertices);
593 cursorX += period->m_advance;
595 break;
599 else if (maxPixelWidth > 0 && cursorX > maxPixelWidth)
600 break; // exceeded max allowed width - stop rendering
602 offsetX = static_cast<float>(
603 MathUtils::round_int(static_cast<double>(itGlyph->m_glyphPosition.x_offset) / 64));
604 offsetY = static_cast<float>(
605 MathUtils::round_int(static_cast<double>(itGlyph->m_glyphPosition.y_offset) / 64));
606 RenderCharacter(context, startX + cursorX + offsetX, startY - offsetY, ch, color, !scrolling,
607 *tempVertices);
608 if (alignment & XBFONT_JUSTIFIED)
610 if ((text[itGlyph->m_glyphInfo.cluster] & 0xffff) == L' ')
611 cursorX += ch->m_advance + spacePerSpaceCharacter;
612 else
613 cursorX += ch->m_advance;
615 else
616 cursorX += ch->m_advance;
617 characters.pop();
619 if (hardwareClipping)
621 CVertexBuffer& vertexBuffer =
622 m_dynamicCache.Lookup(context, dynamicPos, colors, text, rawAlignment, maxPixelWidth,
623 scrolling, std::chrono::steady_clock::now(), dirtyCache);
624 CVertexBuffer newVertexBuffer = CreateVertexBuffer(*tempVertices);
625 vertexBuffer = newVertexBuffer;
626 m_vertexTrans.emplace_back(.0f, .0f, .0f, &vertexBuffer, context.GetClipRegion());
628 else
630 m_staticCache.Lookup(context, staticPos, colors, text, rawAlignment, maxPixelWidth, scrolling,
631 std::chrono::steady_clock::now(), dirtyCache) =
632 *static_cast<CGUIFontCacheStaticValue*>(&tempVertices);
633 /* Append the new vertices to the set collected since the first Begin() call */
634 m_vertex.insert(m_vertex.end(), tempVertices->begin(), tempVertices->end());
637 else
639 if (hardwareClipping)
640 m_vertexTrans.emplace_back(dynamicPos.m_x, dynamicPos.m_y, dynamicPos.m_z, &vertexBuffer,
641 context.GetClipRegion());
642 else
643 /* Append the vertices from the cache to the set collected since the first Begin() call */
644 m_vertex.insert(m_vertex.end(), vertices->begin(), vertices->end());
647 End();
651 float CGUIFontTTF::GetTextWidthInternal(const vecText& text)
653 const std::vector<Glyph> glyphs = GetHarfBuzzShapedGlyphs(text);
654 return GetTextWidthInternal(text, glyphs);
657 // this routine assumes a single line (i.e. it was called from GUITextLayout)
658 float CGUIFontTTF::GetTextWidthInternal(const vecText& text, const std::vector<Glyph>& glyphs)
660 float width = 0;
661 for (auto it = glyphs.begin(); it != glyphs.end(); it++)
663 const character_t ch = text[(*it).m_glyphInfo.cluster];
664 Character* c = GetCharacter(ch, (*it).m_glyphInfo.codepoint);
665 if (c)
667 // If last character in line, we want to add render width
668 // and not advance distance - this makes sure that italic text isn't
669 // choped on the end (as render width is larger than advance then).
670 if (std::next(it) == glyphs.end())
671 width += std::max(c->m_right - c->m_left + c->m_offsetX, c->m_advance);
672 else if ((ch & 0xffff) == static_cast<character_t>('\t'))
673 width += GetTabSpaceLength();
674 else
675 width += c->m_advance;
679 return width;
682 float CGUIFontTTF::GetCharWidthInternal(character_t ch)
684 Character* c = GetCharacter(ch, 0);
685 if (c)
687 if ((ch & 0xffff) == static_cast<character_t>('\t'))
688 return GetTabSpaceLength();
689 else
690 return c->m_advance;
693 return 0;
696 float CGUIFontTTF::GetTextHeight(float lineSpacing, int numLines) const
698 return static_cast<float>(numLines - 1) * GetLineHeight(lineSpacing) + m_cellHeight;
701 float CGUIFontTTF::GetLineHeight(float lineSpacing) const
703 if (!m_face)
704 return 0.0f;
706 return lineSpacing * m_face->size->metrics.height / 64.0f;
709 unsigned int CGUIFontTTF::GetTextureLineHeight() const
711 return m_cellHeight + SPACING_BETWEEN_CHARACTERS_IN_TEXTURE;
714 unsigned int CGUIFontTTF::GetMaxFontHeight() const
716 return m_maxFontHeight + SPACING_BETWEEN_CHARACTERS_IN_TEXTURE;
719 std::vector<CGUIFontTTF::Glyph> CGUIFontTTF::GetHarfBuzzShapedGlyphs(const vecText& text)
721 std::vector<Glyph> glyphs;
722 if (text.empty())
724 return glyphs;
727 std::vector<hb_script_t> scripts;
728 std::vector<RunInfo> runs;
729 hb_unicode_funcs_t* ufuncs = hb_unicode_funcs_get_default();
730 hb_script_t lastScript{HB_SCRIPT_INVALID};
731 int lastScriptIndex = -1;
732 int lastSetIndex = -1;
734 scripts.reserve(text.size());
735 for (const auto& character : text)
737 scripts.emplace_back(hb_unicode_script(ufuncs, static_cast<wchar_t>(0xffff & character)));
740 // HB_SCRIPT_COMMON or HB_SCRIPT_INHERITED should be replaced with previous script
741 for (size_t i = 0; i < scripts.size(); ++i)
743 if (scripts[i] == HB_SCRIPT_COMMON || scripts[i] == HB_SCRIPT_INHERITED)
745 if (lastScriptIndex != -1)
747 scripts[i] = lastScript;
748 lastSetIndex = i;
751 else
753 for (size_t j = lastSetIndex + 1; j < i; ++j)
754 scripts[j] = scripts[i];
755 lastScript = scripts[i];
756 lastScriptIndex = i;
757 lastSetIndex = i;
761 lastScript = scripts[0];
762 int lastRunStart = 0;
764 for (unsigned int i = 0; i <= static_cast<unsigned int>(scripts.size()); ++i)
766 if (i == scripts.size() || scripts[i] != lastScript)
768 RunInfo run{};
769 run.m_startOffset = lastRunStart;
770 run.m_endOffset = i;
771 run.m_script = lastScript;
772 runs.emplace_back(run);
774 if (i < scripts.size())
776 lastScript = scripts[i];
777 lastRunStart = i;
779 else
781 break;
786 for (auto& run : runs)
788 run.m_buffer = hb_buffer_create();
789 hb_buffer_set_direction(run.m_buffer, static_cast<hb_direction_t>(HB_DIRECTION_LTR));
790 hb_buffer_set_script(run.m_buffer, run.m_script);
792 for (unsigned int j = run.m_startOffset; j < run.m_endOffset; j++)
794 hb_buffer_add(run.m_buffer, static_cast<wchar_t>(0xffff & text[j]), j);
797 hb_buffer_set_content_type(run.m_buffer, HB_BUFFER_CONTENT_TYPE_UNICODE);
798 hb_shape(m_hbFont, run.m_buffer, nullptr, 0);
799 unsigned int glyphCount;
800 run.m_glyphInfos = hb_buffer_get_glyph_infos(run.m_buffer, &glyphCount);
801 run.m_glyphPositions = hb_buffer_get_glyph_positions(run.m_buffer, &glyphCount);
803 for (size_t k = 0; k < glyphCount; k++)
805 glyphs.emplace_back(run.m_glyphInfos[k], run.m_glyphPositions[k]);
808 hb_buffer_destroy(run.m_buffer);
811 return glyphs;
814 CGUIFontTTF::Character* CGUIFontTTF::GetCharacter(character_t chr, FT_UInt glyphIndex)
816 const wchar_t letter = static_cast<wchar_t>(chr & 0xffff);
818 // ignore linebreaks
819 if (letter == L'\r')
820 return nullptr;
822 const character_t style = (chr & 0x7000000) >> 24; // style = 0 - 6
824 if (!glyphIndex)
825 glyphIndex = FT_Get_Char_Index(m_face, letter);
827 // quick access to the most frequently used glyphs
828 if (glyphIndex < MAX_GLYPH_IDX)
830 character_t ch = (style << 12) | glyphIndex; // 2^12 = 4096
832 if (ch < LOOKUPTABLE_SIZE && m_charquick[ch])
833 return m_charquick[ch];
836 // letters are stored based on style and glyph
837 character_t ch = (style << 16) | glyphIndex;
839 // perform binary search on sorted array by m_glyphAndStyle and
840 // if not found obtains position to insert the new m_char to keep sorted
841 int low = 0;
842 int high = m_char.size() - 1;
843 while (low <= high)
845 int mid = (low + high) >> 1;
846 if (ch > m_char[mid].m_glyphAndStyle)
847 low = mid + 1;
848 else if (ch < m_char[mid].m_glyphAndStyle)
849 high = mid - 1;
850 else
851 return &m_char[mid];
853 // if we get to here, then low is where we should insert the new character
855 int startIndex = low;
857 // increase the size of the buffer if we need it
858 if (m_char.size() == m_char.capacity())
860 m_char.reserve(m_char.capacity() + CHAR_CHUNK);
861 startIndex = 0;
864 // render the character to our texture
865 // must End() as we can't render text to our texture during a Begin(), End() block
866 unsigned int nestedBeginCount = m_nestedBeginCount;
867 m_nestedBeginCount = 1;
868 if (nestedBeginCount)
869 End();
871 m_char.emplace(m_char.begin() + low);
872 if (!CacheCharacter(glyphIndex, style, m_char.data() + low))
873 { // unable to cache character - try clearing them all out and starting over
874 CLog::LogF(LOGDEBUG, "Unable to cache character. Clearing character cache of {} characters",
875 m_char.size());
876 ClearCharacterCache();
877 low = 0;
878 startIndex = 0;
879 m_char.emplace(m_char.begin());
880 if (!CacheCharacter(glyphIndex, style, m_char.data()))
882 CLog::LogF(LOGERROR, "Unable to cache character (out of memory?)");
883 if (nestedBeginCount)
884 Begin();
885 m_nestedBeginCount = nestedBeginCount;
886 return nullptr;
890 if (nestedBeginCount)
891 Begin();
892 m_nestedBeginCount = nestedBeginCount;
894 // update the lookup table with only the m_char addresses that have changed
895 for (size_t i = startIndex; i < m_char.size(); ++i)
897 if (m_char[i].m_glyphIndex < MAX_GLYPH_IDX)
899 // >> 16 is style (0-6), then 16 - 12 (>> 4) is equivalent to style * 4096
900 character_t ch = ((m_char[i].m_glyphAndStyle & 0xffff0000) >> 4) | m_char[i].m_glyphIndex;
902 if (ch < LOOKUPTABLE_SIZE)
903 m_charquick[ch] = m_char.data() + i;
907 return m_char.data() + low;
910 bool CGUIFontTTF::CacheCharacter(FT_UInt glyphIndex, uint32_t style, Character* ch)
912 FT_Glyph glyph = nullptr;
913 if (FT_Load_Glyph(m_face, glyphIndex, FT_LOAD_TARGET_LIGHT))
915 CLog::LogF(LOGDEBUG, "Failed to load glyph {:x}", glyphIndex);
916 return false;
919 // make bold if applicable
920 if (style & FONT_STYLE_BOLD)
921 SetGlyphStrength(m_face->glyph, GLYPH_STRENGTH_BOLD);
922 // and italics if applicable
923 if (style & FONT_STYLE_ITALICS)
924 ObliqueGlyph(m_face->glyph);
925 // and light if applicable
926 if (style & FONT_STYLE_LIGHT)
927 SetGlyphStrength(m_face->glyph, GLYPH_STRENGTH_LIGHT);
928 // grab the glyph
929 if (FT_Get_Glyph(m_face->glyph, &glyph))
931 CLog::LogF(LOGDEBUG, "Failed to get glyph {:x}", glyphIndex);
932 return false;
934 if (m_stroker)
935 FT_Glyph_StrokeBorder(&glyph, m_stroker, 0, 1);
936 // render the glyph
937 if (FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, nullptr, 1))
939 CLog::LogF(LOGDEBUG, "Failed to render glyph {:x} to a bitmap", glyphIndex);
940 return false;
943 FT_BitmapGlyph bitGlyph = (FT_BitmapGlyph)glyph;
944 FT_Bitmap bitmap = bitGlyph->bitmap;
945 bool isEmptyGlyph = (bitmap.width == 0 || bitmap.rows == 0);
947 if (!isEmptyGlyph)
949 if (bitGlyph->left < 0)
950 m_posX += -bitGlyph->left;
952 // check we have enough room for the character.
953 // cast-fest is here to avoid warnings due to freeetype version differences (signedness of width).
954 if (static_cast<int>(m_posX + bitGlyph->left + bitmap.width +
955 SPACING_BETWEEN_CHARACTERS_IN_TEXTURE) > static_cast<int>(m_textureWidth))
956 { // no space - gotta drop to the next line (which means creating a new texture and copying it across)
957 m_posX = 1;
958 m_posY += GetTextureLineHeight();
959 if (bitGlyph->left < 0)
960 m_posX += -bitGlyph->left;
962 if (m_posY + GetTextureLineHeight() >= m_textureHeight)
964 // create the new larger texture
965 unsigned int newHeight = m_posY + GetTextureLineHeight();
966 // check for max height
967 if (newHeight > m_renderSystem->GetMaxTextureSize())
969 CLog::LogF(LOGDEBUG, "New cache texture is too large ({} > {} pixels long)", newHeight,
970 m_renderSystem->GetMaxTextureSize());
971 FT_Done_Glyph(glyph);
972 return false;
975 std::unique_ptr<CTexture> newTexture = ReallocTexture(newHeight);
976 if (!newTexture)
978 FT_Done_Glyph(glyph);
979 CLog::LogF(LOGDEBUG, "Failed to allocate new texture of height {}", newHeight);
980 return false;
982 m_texture = std::move(newTexture);
984 m_posY = GetMaxFontHeight();
987 if (!m_texture)
989 FT_Done_Glyph(glyph);
990 CLog::LogF(LOGDEBUG, "no texture to cache character to");
991 return false;
995 // set the character in our table
996 ch->m_glyphAndStyle = (style << 16) | glyphIndex;
997 ch->m_glyphIndex = glyphIndex;
998 ch->m_offsetX = static_cast<short>(bitGlyph->left);
999 ch->m_offsetY = static_cast<short>(m_cellBaseLine - bitGlyph->top);
1000 ch->m_left = isEmptyGlyph ? 0.0f : (static_cast<float>(m_posX));
1001 ch->m_top = isEmptyGlyph ? 0.0f : (static_cast<float>(m_posY));
1002 ch->m_right = ch->m_left + bitmap.width;
1003 ch->m_bottom = ch->m_top + bitmap.rows;
1004 ch->m_advance =
1005 static_cast<float>(MathUtils::round_int(static_cast<double>(m_face->glyph->advance.x) / 64));
1007 // we need only render if we actually have some pixels
1008 if (!isEmptyGlyph)
1010 // ensure our rect will stay inside the texture (it *should* but we need to be certain)
1011 unsigned int x1 = std::max(m_posX, 0);
1012 unsigned int y1 = std::max(m_posY, 0);
1013 unsigned int x2 = std::min(x1 + bitmap.width, m_textureWidth);
1014 unsigned int y2 = std::min(y1 + bitmap.rows, m_textureHeight);
1015 m_maxFontHeight = std::max(m_maxFontHeight, y2);
1016 CopyCharToTexture(bitGlyph, x1, y1, x2, y2);
1018 m_posX += SPACING_BETWEEN_CHARACTERS_IN_TEXTURE +
1019 static_cast<unsigned short>(ch->m_right - ch->m_left);
1022 // free the glyph
1023 FT_Done_Glyph(glyph);
1025 return true;
1028 void CGUIFontTTF::RenderCharacter(CGraphicContext& context,
1029 float posX,
1030 float posY,
1031 const Character* ch,
1032 UTILS::COLOR::Color color,
1033 bool roundX,
1034 std::vector<SVertex>& vertices)
1036 // actual image width isn't same as the character width as that is
1037 // just baseline width and height should include the descent
1038 const float width = ch->m_right - ch->m_left;
1039 const float height = ch->m_bottom - ch->m_top;
1041 // return early if nothing to render
1042 if (width == 0 || height == 0)
1043 return;
1045 // posX and posY are relative to our origin, and the textcell is offset
1046 // from our (posX, posY). Plus, these are unscaled quantities compared to the underlying GUI resolution
1047 CRect vertex((posX + ch->m_offsetX) * context.GetGUIScaleX(),
1048 (posY + ch->m_offsetY) * context.GetGUIScaleY(),
1049 (posX + ch->m_offsetX + width) * context.GetGUIScaleX(),
1050 (posY + ch->m_offsetY + height) * context.GetGUIScaleY());
1051 vertex += CPoint(m_originX, m_originY);
1052 CRect texture(ch->m_left, ch->m_top, ch->m_right, ch->m_bottom);
1053 if (!m_renderSystem->ScissorsCanEffectClipping())
1054 context.ClipRect(vertex, texture);
1056 // transform our positions - note, no scaling due to GUI calibration/resolution occurs
1057 float x[VERTEX_PER_GLYPH] = {context.ScaleFinalXCoord(vertex.x1, vertex.y1),
1058 context.ScaleFinalXCoord(vertex.x2, vertex.y1),
1059 context.ScaleFinalXCoord(vertex.x2, vertex.y2),
1060 context.ScaleFinalXCoord(vertex.x1, vertex.y2)};
1062 if (roundX)
1064 // We only round the "left" side of the character, and then use the direction of rounding to
1065 // move the "right" side of the character. This ensures that a constant width is kept when rendering
1066 // the same letter at the same size at different places of the screen, avoiding the problem
1067 // of the "left" side rounding one way while the "right" side rounds the other way, thus getting
1068 // altering the width of thin characters substantially. This only really works for positive
1069 // coordinates (due to the direction of truncation for negatives) but this is the only case that
1070 // really interests us anyway.
1071 float rx0 = static_cast<float>(MathUtils::round_int(static_cast<double>(x[0])));
1072 float rx3 = static_cast<float>(MathUtils::round_int(static_cast<double>(x[3])));
1073 x[1] = static_cast<float>(MathUtils::truncate_int(static_cast<double>(x[1])));
1074 x[2] = static_cast<float>(MathUtils::truncate_int(static_cast<double>(x[2])));
1075 if (x[0] > 0.0f && rx0 > x[0])
1076 x[1] += 1;
1077 else if (x[0] < 0.0f && rx0 < x[0])
1078 x[1] -= 1;
1079 if (x[3] > 0.0f && rx3 > x[3])
1080 x[2] += 1;
1081 else if (x[3] < 0.0f && rx3 < x[3])
1082 x[2] -= 1;
1083 x[0] = rx0;
1084 x[3] = rx3;
1087 const float y[VERTEX_PER_GLYPH] = {
1088 static_cast<float>(MathUtils::round_int(
1089 static_cast<double>(context.ScaleFinalYCoord(vertex.x1, vertex.y1)))),
1090 static_cast<float>(MathUtils::round_int(
1091 static_cast<double>(context.ScaleFinalYCoord(vertex.x2, vertex.y1)))),
1092 static_cast<float>(MathUtils::round_int(
1093 static_cast<double>(context.ScaleFinalYCoord(vertex.x2, vertex.y2)))),
1094 static_cast<float>(MathUtils::round_int(
1095 static_cast<double>(context.ScaleFinalYCoord(vertex.x1, vertex.y2))))};
1097 const float z[VERTEX_PER_GLYPH] = {
1098 static_cast<float>(MathUtils::round_int(
1099 static_cast<double>(context.ScaleFinalZCoord(vertex.x1, vertex.y1)))),
1100 static_cast<float>(MathUtils::round_int(
1101 static_cast<double>(context.ScaleFinalZCoord(vertex.x2, vertex.y1)))),
1102 static_cast<float>(MathUtils::round_int(
1103 static_cast<double>(context.ScaleFinalZCoord(vertex.x2, vertex.y2)))),
1104 static_cast<float>(MathUtils::round_int(
1105 static_cast<double>(context.ScaleFinalZCoord(vertex.x1, vertex.y2))))};
1107 // tex coords converted to 0..1 range
1108 const float tl = texture.x1 * m_textureScaleX;
1109 const float tr = texture.x2 * m_textureScaleX;
1110 const float tt = texture.y1 * m_textureScaleY;
1111 const float tb = texture.y2 * m_textureScaleY;
1113 vertices.resize(vertices.size() + VERTEX_PER_GLYPH);
1114 SVertex* v = &vertices[vertices.size() - VERTEX_PER_GLYPH];
1115 m_color = color;
1117 #if !defined(HAS_DX)
1118 uint8_t r = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::R, color);
1119 uint8_t g = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::G, color);
1120 uint8_t b = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::B, color);
1121 uint8_t a = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::A, color);
1122 #endif
1124 for (int i = 0; i < VERTEX_PER_GLYPH; i++)
1126 #ifdef HAS_DX
1127 CD3DHelper::XMStoreColor(&v[i].col, color);
1128 #else
1129 v[i].r = r;
1130 v[i].g = g;
1131 v[i].b = b;
1132 v[i].a = a;
1133 #endif
1136 #if defined(HAS_DX)
1137 for (int i = 0; i < VERTEX_PER_GLYPH; i++)
1139 v[i].x = x[i];
1140 v[i].y = y[i];
1141 v[i].z = z[i];
1144 v[0].u = tl;
1145 v[0].v = tt;
1147 v[1].u = tr;
1148 v[1].v = tt;
1150 v[2].u = tr;
1151 v[2].v = tb;
1153 v[3].u = tl;
1154 v[3].v = tb;
1155 #else
1156 // GL / GLES uses triangle strips, not quads, so have to rearrange the vertex order
1157 v[0].u = tl;
1158 v[0].v = tt;
1159 v[0].x = x[0];
1160 v[0].y = y[0];
1161 v[0].z = z[0];
1163 v[1].u = tl;
1164 v[1].v = tb;
1165 v[1].x = x[3];
1166 v[1].y = y[3];
1167 v[1].z = z[3];
1169 v[2].u = tr;
1170 v[2].v = tt;
1171 v[2].x = x[1];
1172 v[2].y = y[1];
1173 v[2].z = z[1];
1175 v[3].u = tr;
1176 v[3].v = tb;
1177 v[3].x = x[2];
1178 v[3].y = y[2];
1179 v[3].z = z[2];
1180 #endif
1183 // Oblique code - original taken from freetype2 (ftsynth.c)
1184 void CGUIFontTTF::ObliqueGlyph(FT_GlyphSlot slot)
1186 /* only oblique outline glyphs */
1187 if (slot->format != FT_GLYPH_FORMAT_OUTLINE)
1188 return;
1190 /* we don't touch the advance width */
1192 /* For italic, simply apply a shear transform, with an angle */
1193 /* of about 12 degrees. */
1195 FT_Matrix transform;
1196 transform.xx = 0x10000L;
1197 transform.yx = 0x00000L;
1199 transform.xy = 0x06000L;
1200 transform.yy = 0x10000L;
1202 FT_Outline_Transform(&slot->outline, &transform);
1205 // Embolden code - original taken from freetype2 (ftsynth.c)
1206 void CGUIFontTTF::SetGlyphStrength(FT_GlyphSlot slot, int glyphStrength)
1208 if (slot->format != FT_GLYPH_FORMAT_OUTLINE)
1209 return;
1211 /* some reasonable strength */
1212 FT_Pos strength = FT_MulFix(m_face->units_per_EM, m_face->size->metrics.y_scale) / glyphStrength;
1214 FT_BBox bbox_before, bbox_after;
1215 FT_Outline_Get_CBox(&slot->outline, &bbox_before);
1216 FT_Outline_Embolden(&slot->outline, strength); // ignore error
1217 FT_Outline_Get_CBox(&slot->outline, &bbox_after);
1219 FT_Pos dx = bbox_after.xMax - bbox_before.xMax;
1220 FT_Pos dy = bbox_after.yMax - bbox_before.yMax;
1222 if (slot->advance.x)
1223 slot->advance.x += dx;
1225 if (slot->advance.y)
1226 slot->advance.y += dy;
1228 slot->metrics.width += dx;
1229 slot->metrics.height += dy;
1230 slot->metrics.horiBearingY += dy;
1231 slot->metrics.horiAdvance += dx;
1232 slot->metrics.vertBearingX -= dx / 2;
1233 slot->metrics.vertBearingY += dy;
1234 slot->metrics.vertAdvance += dy;
1237 float CGUIFontTTF::GetTabSpaceLength()
1239 const Character* c = GetCharacter(static_cast<character_t>('X'), 0);
1240 return c ? c->m_advance * TAB_SPACE_LENGTH : 28.0f * TAB_SPACE_LENGTH;