diff options
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | cmake/modules/FindHarfBuzz.cmake | 46 | ||||
-rw-r--r-- | xbmc/guilib/GUIFont.cpp | 3 | ||||
-rw-r--r-- | xbmc/guilib/GUIFontTTF.cpp | 189 | ||||
-rw-r--r-- | xbmc/guilib/GUIFontTTF.h | 53 |
5 files changed, 245 insertions, 47 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index cc6a089d56..a84c3e1082 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,6 +131,7 @@ set(required_deps ASS FreeType FriBidi fstrcmp + HarfBuzz Iconv LibDvd Lzo2 diff --git a/cmake/modules/FindHarfBuzz.cmake b/cmake/modules/FindHarfBuzz.cmake new file mode 100644 index 0000000000..6691136bbb --- /dev/null +++ b/cmake/modules/FindHarfBuzz.cmake @@ -0,0 +1,46 @@ +#.rst: +# FindHarfbuzz +# ------------ +# Finds the HarfBuzz library +# +# This will define the following variables:: +# +# HARFBUZZ_FOUND - system has HarfBuzz +# HARFBUZZ_INCLUDE_DIRS - the HarfBuzz include directory +# HARFBUZZ_LIBRARIES - the HarfBuzz libraries +# +# and the following imported targets:: +# +# HarfBuzz::HarfBuzz - The HarfBuzz library + +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_HARFBUZZ harfbuzz QUIET) +endif() + +find_path(HARFBUZZ_INCLUDE_DIR NAMES harfbuzz/hb-ft.h hb-ft.h + PATHS ${PC_HARFBUZZ_INCLUDEDIR} + ${PC_HARFBUZZ_INCLUDE_DIRS} + PATH_SUFFIXES harfbuzz) +find_library(HARFBUZZ_LIBRARY NAMES harfbuzz harfbuzz + PATHS ${PC_HARFBUZZ_LIBDIR}) + +set(HARFBUZZ_VERSION ${PC_HARFBUZZ_VERSION}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(HarfBuzz + REQUIRED_VARS HARFBUZZ_LIBRARY HARFBUZZ_INCLUDE_DIR + VERSION_VAR HARFBUZZ_VERSION) + +if(HARFBUZZ_FOUND) + set(HARFBUZZ_LIBRARIES ${HARFBUZZ_LIBRARY}) + set(HARFBUZZ_INCLUDE_DIRS ${HARFBUZZ_INCLUDE_DIR}) + + if(NOT TARGET HarfBuzz::HarfBuzz) + add_library(HarfBuzz::HarfBuzz UNKNOWN IMPORTED) + set_target_properties(HarfBuzz::HarfBuzz PROPERTIES + IMPORTED_LOCATION "${HARFBUZZ_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${HARFBUZZ_INCLUDE_DIR}") + endif() +endif() + +mark_as_advanced(HARFBUZZ_INCLUDE_DIR HARFBUZZ_LIBRARY) diff --git a/xbmc/guilib/GUIFont.cpp b/xbmc/guilib/GUIFont.cpp index 29f5b9ea69..a11497c5fb 100644 --- a/xbmc/guilib/GUIFont.cpp +++ b/xbmc/guilib/GUIFont.cpp @@ -237,7 +237,8 @@ float CGUIFont::GetTextWidth( const vecText &text ) { if (!m_font) return 0; CSingleLock lock(CServiceBroker::GetWinSystem()->GetGfxContext()); - return m_font->GetTextWidthInternal(text.begin(), text.end()) * CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIScaleX(); + return m_font->GetTextWidthInternal(text) * + CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIScaleX(); } float CGUIFont::GetCharWidth( character_t ch ) diff --git a/xbmc/guilib/GUIFontTTF.cpp b/xbmc/guilib/GUIFontTTF.cpp index 4cb8e7011d..3443b50293 100644 --- a/xbmc/guilib/GUIFontTTF.cpp +++ b/xbmc/guilib/GUIFontTTF.cpp @@ -27,7 +27,7 @@ // stuff for freetype #include <ft2build.h> - +#include <harfbuzz/hb-ft.h> #if defined(HAS_GL) || defined(HAS_GLES) #include "system_gl.h" #endif @@ -227,6 +227,9 @@ void CGUIFontTTF::Clear() m_posY = 0; m_nestedBeginCount = 0; + if (m_hbFont) + hb_font_destroy(m_hbFont); + m_hbFont = nullptr; if (m_face) g_freeTypeLibrary.ReleaseFont(m_face); m_face = NULL; @@ -250,7 +253,9 @@ bool CGUIFontTTF::Load( if (!m_face) return false; - + m_hbFont = hb_ft_font_create(m_face, 0); + if (!m_hbFont) + return false; /* the values used are described below @@ -325,7 +330,7 @@ bool CGUIFontTTF::Load( m_posY = -(int)GetTextureLineHeight(); // cache the ellipses width - Character *ellipse = GetCharacter(L'.'); + Character* ellipse = GetCharacter(L'.', 0); if (ellipse) m_ellipsesWidth = ellipse->advance; return true; @@ -367,7 +372,7 @@ void CGUIFontTTF::DrawTextInternal(float x, } Begin(); - + std::vector<Glyph> glyphs = GetHarfBuzzShapedGlyphs(text); uint32_t rawAlignment = alignment; bool dirtyCache(false); bool hardwareClipping = m_renderSystem->ScissorsCanEffectClipping(); @@ -406,7 +411,7 @@ void CGUIFontTTF::DrawTextInternal(float x, // Check if we will really need to truncate or justify the text if ( alignment & XBFONT_TRUNCATED ) { - if ( maxPixelWidth <= 0.0f || GetTextWidthInternal(text.begin(), text.end()) <= maxPixelWidth) + if (maxPixelWidth <= 0.0f || GetTextWidthInternal(text, glyphs) <= maxPixelWidth) alignment &= ~XBFONT_TRUNCATED; } else if ( alignment & XBFONT_JUSTIFIED ) @@ -422,7 +427,7 @@ void CGUIFontTTF::DrawTextInternal(float x, if ( alignment & (XBFONT_RIGHT | XBFONT_CENTER_X) ) { // Get the extent of this line - float w = GetTextWidthInternal( text.begin(), text.end() ); + float w = GetTextWidthInternal(text, glyphs); if ( alignment & XBFONT_TRUNCATED && w > maxPixelWidth + 0.5f ) // + 0.5f due to rounding issues w = maxPixelWidth; @@ -439,12 +444,12 @@ void CGUIFontTTF::DrawTextInternal(float x, // first compute the size of the text to render in both characters and pixels unsigned int numSpaces = 0; float linePixels = 0; - for (const auto& pos : text) + for (const auto& glyph : glyphs) { - Character* ch = GetCharacter(pos); + Character* ch = GetCharacter(text[glyph.glyphInfo.cluster], glyph.glyphInfo.codepoint); if (ch) { - if ((pos & 0xffff) == L' ') + if ((text[glyph.glyphInfo.cluster] & 0xffff) == L' ') numSpaces += 1; linePixels += ch->advance; } @@ -454,16 +459,18 @@ void CGUIFontTTF::DrawTextInternal(float x, } float cursorX = 0; // current position along the line + float offsetX = 0; + float offsetY = 0; // Collect all the Character info in a first pass, in case any of them // are not currently cached and cause the texture to be enlarged, which // would invalidate the texture coordinates. std::queue<Character> characters; if (alignment & XBFONT_TRUNCATED) - GetCharacter(L'.'); - for (const auto& pos : text) + GetCharacter(L'.', 0); + for (const auto& glyph : glyphs) { - Character* ch = GetCharacter(pos); + Character* ch = GetCharacter(text[glyph.glyphInfo.cluster], glyph.glyphInfo.codepoint); if (!ch) { Character null = {}; @@ -478,19 +485,18 @@ void CGUIFontTTF::DrawTextInternal(float x, cursorX += ch->advance; } cursorX = 0; - - for (const auto& pos : text) + for (const auto& glyph : glyphs) { // If starting text on a new line, determine justification effects // Get the current letter in the CStdString - UTILS::Color color = (pos & 0xff0000) >> 16; + UTILS::Color color = (text[glyph.glyphInfo.cluster] & 0xff0000) >> 16; if (color >= colors.size()) color = 0; color = colors[color]; // grab the next character Character *ch = &characters.front(); - if (ch->letterAndStyle == 0) + if (ch->letter == 0) { characters.pop(); continue; @@ -503,7 +509,7 @@ void CGUIFontTTF::DrawTextInternal(float x, { // Yup. Let's draw the ellipses, then bail // Perhaps we should really bail to the next line in this case?? - Character *period = GetCharacter(L'.'); + Character* period = GetCharacter(L'.', 0); if (!period) break; @@ -518,10 +524,15 @@ void CGUIFontTTF::DrawTextInternal(float x, else if (maxPixelWidth > 0 && cursorX > maxPixelWidth) break; // exceeded max allowed width - stop rendering - RenderCharacter(startX + cursorX, startY, ch, color, !scrolling, *tempVertices); + offsetX = static_cast<float>( + MathUtils::round_int(static_cast<double>(glyph.glyphPosition.x_offset) / 64)); + offsetY = static_cast<float>( + MathUtils::round_int(static_cast<double>(glyph.glyphPosition.y_offset) / 64)); + RenderCharacter(startX + cursorX + offsetX, startY - offsetY, ch, color, !scrolling, + *tempVertices); if ( alignment & XBFONT_JUSTIFIED ) { - if ((pos & 0xffff) == L' ') + if ((text[glyph.glyphInfo.cluster] & 0xffff) == L' ') cursorX += ch->advance + spacePerSpaceCharacter; else cursorX += ch->advance; @@ -568,19 +579,26 @@ void CGUIFontTTF::DrawTextInternal(float x, End(); } + +float CGUIFontTTF::GetTextWidthInternal(const vecText& text) +{ + std::vector<Glyph> glyphs = GetHarfBuzzShapedGlyphs(text); + return GetTextWidthInternal(text, glyphs); +} + // this routine assumes a single line (i.e. it was called from GUITextLayout) -float CGUIFontTTF::GetTextWidthInternal(vecText::const_iterator start, vecText::const_iterator end) +float CGUIFontTTF::GetTextWidthInternal(const vecText& text, std::vector<Glyph>& glyphs) { float width = 0; - while (start != end) + for (auto it = glyphs.begin(); it != glyphs.end(); it++) { - Character *c = GetCharacter(*start++); + Character* c = GetCharacter(text[(*it).glyphInfo.cluster], (*it).glyphInfo.codepoint); if (c) { // If last character in line, we want to add render width // and not advance distance - this makes sure that italic text isn't // choped on the end (as render width is larger than advance then). - if (start == end) + if (it == glyphs.end()) width += std::max(c->right - c->left + c->offsetX, c->advance); else width += c->advance; @@ -591,7 +609,7 @@ float CGUIFontTTF::GetTextWidthInternal(vecText::const_iterator start, vecText:: float CGUIFontTTF::GetCharWidthInternal(character_t ch) { - Character *c = GetCharacter(ch); + Character* c = GetCharacter(ch, 0); if (c) return c->advance; return 0; } @@ -615,7 +633,99 @@ unsigned int CGUIFontTTF::GetTextureLineHeight() const return m_cellHeight + spacing_between_characters_in_texture; } -CGUIFontTTF::Character* CGUIFontTTF::GetCharacter(character_t chr) +std::vector<CGUIFontTTF::Glyph> CGUIFontTTF::GetHarfBuzzShapedGlyphs(const vecText& text) +{ + std::vector<Glyph> glyphs; + if (text.empty()) + { + return glyphs; + } + std::vector<hb_script_t> scripts; + std::vector<RunInfo> runs; + hb_unicode_funcs_t* ufuncs = hb_unicode_funcs_get_default(); + hb_script_t lastScript; + int lastScriptIndex = -1; + int lastSetIndex = -1; + + for (const auto& character : text) + { + scripts.emplace_back(hb_unicode_script(ufuncs, static_cast<wchar_t>(0xffff & character))); + } + + // HB_SCRIPT_COMMON or HB_SCRIPT_INHERITED should be replaced with previous script + for (unsigned int i = 0; i < scripts.size(); i++) + { + if (scripts[i] == HB_SCRIPT_COMMON || scripts[i] == HB_SCRIPT_INHERITED) + { + if (lastScriptIndex != -1) + { + scripts[i] = lastScript; + lastSetIndex = i; + } + } + else + { + for (unsigned int j = lastSetIndex + 1; j < i; j++) + scripts[j] = scripts[i]; + lastScript = scripts[i]; + lastScriptIndex = i; + lastSetIndex = i; + } + } + + lastScript = scripts[0]; + int lastRunStart = 0; + + for (unsigned int i = 0; i <= scripts.size(); i++) + { + if (i == scripts.size() || scripts[i] != lastScript) + { + RunInfo run{}; + run.startOffset = lastRunStart; + run.endOffset = i; + run.script = lastScript; + runs.emplace_back(run); + + if (i < scripts.size()) + { + lastScript = scripts[i]; + lastRunStart = i; + } + else + { + break; + } + } + } + + for (auto& run : runs) + { + run.buffer = hb_buffer_create(); + hb_buffer_set_direction(run.buffer, static_cast<hb_direction_t>(HB_DIRECTION_LTR)); + hb_buffer_set_script(run.buffer, run.script); + + for (int j = run.startOffset; j < run.endOffset; j++) + { + hb_buffer_add(run.buffer, static_cast<wchar_t>(0xffff & text[j]), j); + } + + hb_buffer_set_content_type(run.buffer, HB_BUFFER_CONTENT_TYPE_UNICODE); + hb_shape(m_hbFont, run.buffer, nullptr, 0); + unsigned int glyphCount; + run.glyphInfos = hb_buffer_get_glyph_infos(run.buffer, &glyphCount); + run.glyphPositions = hb_buffer_get_glyph_positions(run.buffer, &glyphCount); + + for (size_t k = 0; k < glyphCount; k++) + { + glyphs.emplace_back(run.glyphInfos[k], run.glyphPositions[k]); + } + + hb_buffer_destroy(run.buffer); + } + return glyphs; +} + +CGUIFontTTF::Character* CGUIFontTTF::GetCharacter(character_t chr, FT_UInt glyphIndex) { wchar_t letter = (wchar_t)(chr & 0xffff); character_t style = (chr & 0x7000000) >> 24; @@ -627,22 +737,22 @@ CGUIFontTTF::Character* CGUIFontTTF::GetCharacter(character_t chr) // quick access to ascii chars if (letter < 255) { - character_t ch = (style << 8) | letter; + character_t ch = (style << 8) | glyphIndex; if (ch < LOOKUPTABLE_SIZE && m_charquick[ch]) return m_charquick[ch]; } - // letters are stored based on style and letter - character_t ch = (style << 16) | letter; + // letters are stored based on style and glyph + character_t ch = (style << 16) | glyphIndex; int low = 0; int high = m_numChars - 1; while (low <= high) { int mid = (low + high) >> 1; - if (ch > m_char[mid].letterAndStyle) + if (ch > m_char[mid].glyphAndStyle) low = mid + 1; - else if (ch < m_char[mid].letterAndStyle) + else if (ch < m_char[mid].glyphAndStyle) high = mid - 1; else return &m_char[mid]; @@ -672,13 +782,13 @@ CGUIFontTTF::Character* CGUIFontTTF::GetCharacter(character_t chr) unsigned int nestedBeginCount = m_nestedBeginCount; m_nestedBeginCount = 1; if (nestedBeginCount) End(); - if (!CacheCharacter(letter, style, m_char + low)) + if (!CacheCharacter(letter, style, m_char + low, glyphIndex)) { // unable to cache character - try clearing them all out and starting over CLog::Log(LOGDEBUG, "{}: Unable to cache character. Clearing character cache of {} characters", __FUNCTION__, m_numChars); ClearCharacterCache(); low = 0; - if (!CacheCharacter(letter, style, m_char + low)) + if (!CacheCharacter(letter, style, m_char + low, glyphIndex)) { CLog::Log(LOGERROR, "{}: Unable to cache character (out of memory?)", __FUNCTION__); if (nestedBeginCount) Begin(); @@ -693,9 +803,9 @@ CGUIFontTTF::Character* CGUIFontTTF::GetCharacter(character_t chr) memset(m_charquick, 0, sizeof(m_charquick)); for(int i=0;i<m_numChars;i++) { - if ((m_char[i].letterAndStyle & 0xffff) < 255) + if (m_char[i].letter < 255) { - character_t ch = ((m_char[i].letterAndStyle & 0xffff0000) >> 8) | (m_char[i].letterAndStyle & 0xff); + character_t ch = ((m_char[i].glyphAndStyle & 0xffff0000) >> 8) | (m_char[i].glyphIndex); m_charquick[ch] = m_char+i; } } @@ -703,12 +813,13 @@ CGUIFontTTF::Character* CGUIFontTTF::GetCharacter(character_t chr) return m_char + low; } -bool CGUIFontTTF::CacheCharacter(wchar_t letter, uint32_t style, Character* ch) +bool CGUIFontTTF::CacheCharacter(wchar_t letter, uint32_t style, Character* ch, FT_UInt glyphIndex) { - int glyph_index = FT_Get_Char_Index( m_face, letter ); + if (!glyphIndex) + glyphIndex = FT_Get_Char_Index(m_face, letter); FT_Glyph glyph = NULL; - if (FT_Load_Glyph( m_face, glyph_index, FT_LOAD_TARGET_LIGHT )) + if (FT_Load_Glyph(m_face, glyphIndex, FT_LOAD_TARGET_LIGHT)) { CLog::Log(LOGDEBUG, "{} Failed to load glyph {:x}", __FUNCTION__, static_cast<uint32_t>(letter)); @@ -790,7 +901,9 @@ bool CGUIFontTTF::CacheCharacter(wchar_t letter, uint32_t style, Character* ch) } } // set the character in our table - ch->letterAndStyle = (style << 16) | letter; + ch->glyphAndStyle = (style << 16) | glyphIndex; + ch->glyphIndex = glyphIndex; + ch->letter = letter; ch->offsetX = (short)bitGlyph->left; ch->offsetY = (short)m_cellBaseLine - bitGlyph->top; ch->left = isEmptyGlyph ? 0 : ((float)m_posX + ch->offsetX); diff --git a/xbmc/guilib/GUIFontTTF.h b/xbmc/guilib/GUIFontTTF.h index b677a806ec..336cd349c2 100644 --- a/xbmc/guilib/GUIFontTTF.h +++ b/xbmc/guilib/GUIFontTTF.h @@ -8,13 +8,17 @@ #pragma once -#include <string> +#include "utils/Color.h" +#include "utils/Geometry.h" +#include "utils/auto_buffer.h" + #include <stdint.h> +#include <string> #include <vector> -#include "utils/auto_buffer.h" -#include "utils/Color.h" -#include "utils/Geometry.h" +#include <ft2build.h> +#include FT_FREETYPE_H +#include <harfbuzz/hb.h> #ifdef HAS_DX #include <DirectXMath.h> @@ -88,17 +92,48 @@ public: protected: explicit CGUIFontTTF(const std::string& strFileName); + + struct Glyph + { + hb_glyph_info_t glyphInfo; + hb_glyph_position_t glyphPosition; + + // converter for harfbuzz library + Glyph(hb_glyph_info_t gInfo, hb_glyph_position_t gPos) + { + glyphInfo = gInfo; + glyphPosition = gPos; + } + Glyph() {} + }; + struct Character { short offsetX, offsetY; float left, top, right, bottom; float advance; - character_t letterAndStyle; + FT_UInt glyphIndex; + character_t glyphAndStyle; + wchar_t letter; }; + + struct RunInfo + { + int startOffset; + int endOffset; + hb_buffer_t* buffer; + hb_script_t script; + hb_glyph_info_t* glyphInfos; + hb_glyph_position_t* glyphPositions; + }; + void AddReference(); void RemoveReference(); - float GetTextWidthInternal(vecText::const_iterator start, vecText::const_iterator end); + std::vector<Glyph> GetHarfBuzzShapedGlyphs(const vecText& text); + + float GetTextWidthInternal(const vecText& text); + float GetTextWidthInternal(const vecText& text, std::vector<Glyph>& glyph); float GetCharWidthInternal(character_t ch); float GetTextHeight(float lineSpacing, int numLines) const; float GetTextBaseLine() const { return (float)m_cellBaseLine; } @@ -112,8 +147,8 @@ protected: std::string m_strFilename; // Stuff for pre-rendering for speed - inline Character *GetCharacter(character_t letter); - bool CacheCharacter(wchar_t letter, uint32_t style, Character *ch); + Character* GetCharacter(character_t letter, FT_UInt glyphIndex); + bool CacheCharacter(wchar_t letter, uint32_t style, Character* ch, FT_UInt glyphIndex); void RenderCharacter(float posX, float posY, const Character *ch, UTILS::Color color, bool roundX, std::vector<SVertex> &vertices); void ClearCharacterCache(); @@ -156,6 +191,8 @@ protected: FT_Face m_face; FT_Stroker m_stroker; + hb_font_t* m_hbFont = nullptr; + float m_originX; float m_originY; |