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.
9 #include "GUIFontTTF.h"
11 #include "GUIFontManager.h"
12 #include "ServiceBroker.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"
31 #include <harfbuzz/hb-ft.h>
32 #if defined(HAS_GL) || defined(HAS_GLES)
33 #include "utils/GLUtils.h"
35 #include "system_gl.h"
39 #include "guilib/D3DResource.h"
42 #ifdef TARGET_WINDOWS_STORE
43 #define generic GenericFromFreeTypeLibrary
46 #include FT_FREETYPE_H
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;
64 class CFreeTypeLibrary
67 CFreeTypeLibrary() = default;
68 virtual ~CFreeTypeLibrary()
71 FT_Done_FreeType(m_library
);
74 FT_Face
GetFont(const std::string
& filename
,
77 std::vector
<uint8_t>& memoryBuf
)
79 // don't have it yet - create it
81 FT_Init_FreeType(&m_library
);
84 CLog::LogF(LOGERROR
, "Unable to initialize freetype library");
90 // ok, now load the font face
91 CURL
realFile(CSpecialProtocol::TranslatePath(filename
));
92 if (realFile
.GetFileName().empty())
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
104 if (f
.LoadFile(realFile
, memoryBuf
) <= 0)
107 if (FT_New_Memory_Face(m_library
, reinterpret_cast<const FT_Byte
*>(memoryBuf
.data()),
108 memoryBuf
.size(), 0, &face
) != 0)
111 #ifndef TARGET_WINDOWS
112 else if (FT_New_Face(m_library
, realFile
.GetFileName().c_str(), 0, &face
))
114 #endif // ! TARGET_WINDOWS
116 unsigned int ydpi
= 72; // 72 points to the inch is the freetype default
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
))
133 FT_Stroker
GetStroker()
139 if (FT_Stroker_New(m_library
, &stroker
))
145 static void ReleaseFont(FT_Face face
)
151 static void ReleaseStroker(FT_Stroker stroker
)
154 FT_Stroker_Done(stroker
);
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)
177 void CGUIFontTTF::AddReference()
182 void CGUIFontTTF::RemoveReference()
184 // delete this object when it's reference count hits zero
186 if (!m_referenceCount
)
187 g_fontManager
.FreeFontFile(this);
191 void CGUIFontTTF::ClearCharacterCache()
195 DeleteHardwareTexture();
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());
207 void CGUIFontTTF::Clear()
211 memset(m_charquick
, 0, sizeof(m_charquick
));
214 m_nestedBeginCount
= 0;
217 hb_font_destroy(m_hbFont
);
220 g_freeTypeLibrary
.ReleaseFont(m_face
);
223 g_freeTypeLibrary
.ReleaseStroker(m_stroker
);
226 m_vertexTrans
.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
);
241 m_hbFont
= hb_ft_font_create(m_face
, 0);
245 the values used are described below
247 XBMC coords Freetype coords
249 0 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ bbox.yMax, ascender
253 AAAAA pppp cellAscender
256 m_cellBaseLine _ _A_ _A_ pppp_ _ _ _ _/_ _ _ _ _ 0, base line.
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
);
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;
275 cellDescender
-= strength
;
276 cellAscender
+= strength
;
278 m_stroker
= g_freeTypeLibrary
.GetStroker();
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
;
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
;
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());
313 void CGUIFontTTF::Begin()
315 if (m_nestedBeginCount
== 0 && m_texture
&& FirstBegin())
317 m_vertexTrans
.clear();
320 // Keep track of the nested begin/end calls.
321 m_nestedBeginCount
++;
324 void CGUIFontTTF::End()
326 if (m_nestedBeginCount
== 0)
329 if (--m_nestedBeginCount
> 0)
335 void CGUIFontTTF::DrawTextInternal(CGraphicContext
& context
,
338 const std::vector
<UTILS::COLOR::Color
>& colors
,
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
)
358 CGUIFontCacheDynamicPosition(context
.ScaleFinalXCoord(x
, y
), context
.ScaleFinalYCoord(x
, y
),
359 context
.ScaleFinalZCoord(x
, y
));
362 CVertexBuffer unusedVertexBuffer
;
363 CVertexBuffer
& vertexBuffer
=
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
);
383 if (m_vertex
.capacity() == 0)
384 m_vertex
.reserve(VERTEX_PER_GLYPH
* MAX_GLYPHS_PER_TEXT_LINE
);
389 const std::vector
<Glyph
> glyphs
= GetHarfBuzzShapedGlyphs(text
);
390 // save the origin, which is scaled separately
394 // cache the ellipses width
395 if (!m_ellipseCached
)
397 m_ellipseCached
= true;
398 Character
* ellipse
= GetCharacter(L
'.', 0);
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
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
435 if (alignment
& XBFONT_CENTER_X
)
437 // Offset this line's starting position
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
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
)
454 GetCharacter(text
[itRGlyph
->m_glyphInfo
.cluster
], itRGlyph
->m_glyphInfo
.codepoint
);
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());
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
);
480 if ((text
[glyph
.m_glyphInfo
.cluster
] & 0xffff) == L
' ')
482 linePixels
+= ch
->m_advance
;
486 spacePerSpaceCharacter
= (maxPixelWidth
- linePixels
) / numSpaces
;
489 float cursorX
= 0; // current position along the line
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
)
506 GetCharacter(text
[itGlyph
->m_glyphInfo
.cluster
], itGlyph
->m_glyphInfo
.codepoint
);
510 characters
.push(null
);
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
)
529 cursorX
+= ch
->m_advance
;
532 // Reserve vector space: 4 vertex for each glyph
533 tempVertices
->reserve(VERTEX_PER_GLYPH
* glyphs
.size());
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())
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
);
557 if (alignment
& XBFONT_TRUNCATED
)
559 if (alignment
& XBFONT_RIGHT
)
561 if (isBeginWithEllipses
)
563 isBeginWithEllipses
= false;
564 Character
* period
= GetCharacter(L
'.', 0);
568 for (int i
= 0; i
< 3; i
++)
570 RenderCharacter(context
, startX
+ cursorX
, startY
, period
, color
, !scrolling
,
572 cursorX
+= period
->m_advance
;
575 if (maxPixelWidth
> 0 && cursorX
> maxPixelWidth
)
576 break; // exceeded max allowed width - stop rendering
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);
589 for (int i
= 0; i
< 3; i
++)
591 RenderCharacter(context
, startX
+ cursorX
, startY
, period
, color
, !scrolling
,
593 cursorX
+= period
->m_advance
;
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
,
608 if (alignment
& XBFONT_JUSTIFIED
)
610 if ((text
[itGlyph
->m_glyphInfo
.cluster
] & 0xffff) == L
' ')
611 cursorX
+= ch
->m_advance
+ spacePerSpaceCharacter
;
613 cursorX
+= ch
->m_advance
;
616 cursorX
+= ch
->m_advance
;
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());
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());
639 if (hardwareClipping
)
640 m_vertexTrans
.emplace_back(dynamicPos
.m_x
, dynamicPos
.m_y
, dynamicPos
.m_z
, &vertexBuffer
,
641 context
.GetClipRegion());
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());
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
)
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
);
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();
675 width
+= c
->m_advance
;
682 float CGUIFontTTF::GetCharWidthInternal(character_t ch
)
684 Character
* c
= GetCharacter(ch
, 0);
687 if ((ch
& 0xffff) == static_cast<character_t
>('\t'))
688 return GetTabSpaceLength();
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
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
;
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
;
753 for (size_t j
= lastSetIndex
+ 1; j
< i
; ++j
)
754 scripts
[j
] = scripts
[i
];
755 lastScript
= scripts
[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
)
769 run
.m_startOffset
= lastRunStart
;
771 run
.m_script
= lastScript
;
772 runs
.emplace_back(run
);
774 if (i
< scripts
.size())
776 lastScript
= scripts
[i
];
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
);
814 CGUIFontTTF::Character
* CGUIFontTTF::GetCharacter(character_t chr
, FT_UInt glyphIndex
)
816 const wchar_t letter
= static_cast<wchar_t>(chr
& 0xffff);
822 const character_t style
= (chr
& 0x7000000) >> 24; // style = 0 - 6
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
842 int high
= m_char
.size() - 1;
845 int mid
= (low
+ high
) >> 1;
846 if (ch
> m_char
[mid
].m_glyphAndStyle
)
848 else if (ch
< m_char
[mid
].m_glyphAndStyle
)
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
);
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
)
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",
876 ClearCharacterCache();
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
)
885 m_nestedBeginCount
= nestedBeginCount
;
890 if (nestedBeginCount
)
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
);
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
);
929 if (FT_Get_Glyph(m_face
->glyph
, &glyph
))
931 CLog::LogF(LOGDEBUG
, "Failed to get glyph {:x}", glyphIndex
);
935 FT_Glyph_StrokeBorder(&glyph
, m_stroker
, 0, 1);
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
);
943 FT_BitmapGlyph bitGlyph
= (FT_BitmapGlyph
)glyph
;
944 FT_Bitmap bitmap
= bitGlyph
->bitmap
;
945 bool isEmptyGlyph
= (bitmap
.width
== 0 || bitmap
.rows
== 0);
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)
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
);
975 std::unique_ptr
<CTexture
> newTexture
= ReallocTexture(newHeight
);
978 FT_Done_Glyph(glyph
);
979 CLog::LogF(LOGDEBUG
, "Failed to allocate new texture of height {}", newHeight
);
982 m_texture
= std::move(newTexture
);
984 m_posY
= GetMaxFontHeight();
989 FT_Done_Glyph(glyph
);
990 CLog::LogF(LOGDEBUG
, "no texture to cache character to");
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
;
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
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
);
1023 FT_Done_Glyph(glyph
);
1028 void CGUIFontTTF::RenderCharacter(CGraphicContext
& context
,
1031 const Character
* ch
,
1032 UTILS::COLOR::Color color
,
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)
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
)};
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])
1077 else if (x
[0] < 0.0f
&& rx0
< x
[0])
1079 if (x
[3] > 0.0f
&& rx3
> x
[3])
1081 else if (x
[3] < 0.0f
&& rx3
< x
[3])
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
];
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
);
1124 for (int i
= 0; i
< VERTEX_PER_GLYPH
; i
++)
1127 CD3DHelper::XMStoreColor(&v
[i
].col
, color
);
1137 for (int i
= 0; i
< VERTEX_PER_GLYPH
; i
++)
1156 // GL / GLES uses triangle strips, not quads, so have to rearrange the vertex order
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
)
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
)
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
;