Use SheenBidi instead of FriBidi and libraqm
This commit is contained in:
parent
13db1b83c1
commit
369c346857
@ -228,13 +228,12 @@ Software used
|
|||||||
Software used
|
Software used
|
||||||
- ogg
|
- ogg
|
||||||
- vorbis
|
- vorbis
|
||||||
- fribidi
|
- SheenBidi
|
||||||
- blender
|
- blender
|
||||||
- wiiuse
|
- wiiuse
|
||||||
- harfbuzz
|
- harfbuzz
|
||||||
|
|
||||||
Software used
|
Software used
|
||||||
- libraqm
|
|
||||||
- sqlite
|
- sqlite
|
||||||
- openssl
|
- openssl
|
||||||
- mcpp
|
- mcpp
|
||||||
|
@ -30,9 +30,11 @@
|
|||||||
#include "utils/translation.hpp"
|
#include "utils/translation.hpp"
|
||||||
|
|
||||||
#ifndef SERVER_ONLY
|
#ifndef SERVER_ONLY
|
||||||
#include <fribidi/fribidi.h>
|
#include <harfbuzz/hb-ft.h>
|
||||||
#include <harfbuzz/hb.h>
|
extern "C"
|
||||||
#include <raqm.h>
|
{
|
||||||
|
#include <SheenBidi.h>
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
FontManager *font_manager = NULL;
|
FontManager *font_manager = NULL;
|
||||||
@ -46,9 +48,11 @@ FontManager::FontManager()
|
|||||||
m_ft_library = NULL;
|
m_ft_library = NULL;
|
||||||
m_digit_face = NULL;
|
m_digit_face = NULL;
|
||||||
m_shaping_dpi = 128;
|
m_shaping_dpi = 128;
|
||||||
|
m_hb_buffer = NULL;
|
||||||
if (GUIEngine::isNoGraphics())
|
if (GUIEngine::isNoGraphics())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
m_hb_buffer = hb_buffer_create();
|
||||||
checkFTError(FT_Init_FreeType(&m_ft_library), "loading freetype library");
|
checkFTError(FT_Init_FreeType(&m_ft_library), "loading freetype library");
|
||||||
#endif
|
#endif
|
||||||
} // FontManager
|
} // FontManager
|
||||||
@ -66,6 +70,9 @@ FontManager::~FontManager()
|
|||||||
if (GUIEngine::isNoGraphics())
|
if (GUIEngine::isNoGraphics())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
hb_buffer_destroy(m_hb_buffer);
|
||||||
|
for (hb_font_t* font : m_hb_fonts)
|
||||||
|
hb_font_destroy(font);
|
||||||
for (unsigned int i = 0; i < m_faces.size(); i++)
|
for (unsigned int i = 0; i < m_faces.size(); i++)
|
||||||
checkFTError(FT_Done_Face(m_faces[i]), "removing faces for shaping");
|
checkFTError(FT_Done_Face(m_faces[i]), "removing faces for shaping");
|
||||||
if (m_digit_face != NULL)
|
if (m_digit_face != NULL)
|
||||||
@ -253,60 +260,37 @@ namespace LineBreakingRules
|
|||||||
} // insertBreakMark
|
} // insertBreakMark
|
||||||
} // namespace LineBreakingRules
|
} // namespace LineBreakingRules
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
namespace RTLRules
|
|
||||||
{
|
|
||||||
//-------------------------------------------------------------------------
|
|
||||||
void insertRTLMark(const std::u32string& str, std::vector<bool>& line,
|
|
||||||
std::vector<bool>& char_bool)
|
|
||||||
{
|
|
||||||
// Check if first character has strong RTL, then consider this line is
|
|
||||||
// RTL
|
|
||||||
std::vector<FriBidiCharType> types;
|
|
||||||
std::vector<FriBidiLevel> levels;
|
|
||||||
types.resize(str.size(), 0);
|
|
||||||
levels.resize(str.size(), 0);
|
|
||||||
FriBidiParType par_type = FRIBIDI_PAR_ON;
|
|
||||||
const FriBidiChar* fribidi_str = (const FriBidiChar*)str.c_str();
|
|
||||||
fribidi_get_bidi_types(fribidi_str, str.size(), types.data());
|
|
||||||
#if FRIBIDI_MAJOR_VERSION >= 1
|
|
||||||
std::vector<FriBidiBracketType> btypes;
|
|
||||||
btypes.resize(str.size(), 0);
|
|
||||||
fribidi_get_bracket_types(fribidi_str, str.size(), types.data(),
|
|
||||||
btypes.data());
|
|
||||||
int max_level = fribidi_get_par_embedding_levels_ex(types.data(),
|
|
||||||
btypes.data(), str.size(), &par_type, levels.data());
|
|
||||||
#else
|
|
||||||
int max_level = fribidi_get_par_embedding_levels(types.data(),
|
|
||||||
str.size(), &par_type, levels.data());
|
|
||||||
#endif
|
|
||||||
(void)max_level;
|
|
||||||
bool cur_rtl = par_type == FRIBIDI_PAR_RTL;
|
|
||||||
if (cur_rtl)
|
|
||||||
{
|
|
||||||
for (unsigned i = 0; i < line.size(); i++)
|
|
||||||
line[i] = true;
|
|
||||||
}
|
|
||||||
int cur_level = levels[0];
|
|
||||||
for (unsigned i = 0; i < char_bool.size(); i++)
|
|
||||||
{
|
|
||||||
if (levels[i] != cur_level)
|
|
||||||
{
|
|
||||||
cur_rtl = !cur_rtl;
|
|
||||||
cur_level = levels[i];
|
|
||||||
}
|
|
||||||
char_bool[i] = cur_rtl;
|
|
||||||
}
|
|
||||||
} // insertRTLMark
|
|
||||||
} // namespace RTLRules
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
/* Turn text into glyph layout for rendering by libraqm. */
|
/* Turn text into glyph layout for rendering by libraqm. */
|
||||||
void FontManager::shape(const std::u32string& text,
|
void FontManager::shape(const std::u32string& text,
|
||||||
std::vector<irr::gui::GlyphLayout>& gls,
|
std::vector<irr::gui::GlyphLayout>& gls,
|
||||||
std::vector<std::u32string>* line_data)
|
std::vector<std::u32string>* line_data)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
// Helper struct
|
||||||
|
struct ShapeGlyph
|
||||||
|
{
|
||||||
|
unsigned int index;
|
||||||
|
int x_advance;
|
||||||
|
int y_advance;
|
||||||
|
int x_offset;
|
||||||
|
int y_offset;
|
||||||
|
uint32_t cluster;
|
||||||
|
FT_Face ftface;
|
||||||
|
};
|
||||||
|
auto fill_shape_glyph = [](std::vector<ShapeGlyph>& shape_glyphs,
|
||||||
|
hb_buffer_t* hb_buffer, int offset, FT_Face ftface)
|
||||||
|
{
|
||||||
|
size_t len = hb_buffer_get_length(hb_buffer);
|
||||||
|
hb_glyph_info_t* info = hb_buffer_get_glyph_infos(hb_buffer, NULL);
|
||||||
|
hb_glyph_position_t* position =
|
||||||
|
hb_buffer_get_glyph_positions(hb_buffer, NULL);
|
||||||
|
for (size_t i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
shape_glyphs.push_back({info[i].codepoint, position[i].x_advance,
|
||||||
|
position[i].y_advance, position[i].x_offset,
|
||||||
|
position[i].y_offset, info[i].cluster + offset, ftface});
|
||||||
|
}
|
||||||
|
};
|
||||||
// m_faces can be empty in null device
|
// m_faces can be empty in null device
|
||||||
if (text.empty() || m_faces.empty())
|
if (text.empty() || m_faces.empty())
|
||||||
return;
|
return;
|
||||||
@ -319,6 +303,7 @@ void FontManager::shape(const std::u32string& text,
|
|||||||
|
|
||||||
for (unsigned l = 0; l < lines.size(); l++)
|
for (unsigned l = 0; l < lines.size(); l++)
|
||||||
{
|
{
|
||||||
|
std::vector<ShapeGlyph> glyphs;
|
||||||
if (l != 0)
|
if (l != 0)
|
||||||
{
|
{
|
||||||
gui::GlyphLayout gl = { 0 };
|
gui::GlyphLayout gl = { 0 };
|
||||||
@ -336,32 +321,43 @@ void FontManager::shape(const std::u32string& text,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
raqm_t* rq = raqm_create();
|
SBCodepointSequence codepoint_sequence;
|
||||||
if (!rq)
|
codepoint_sequence.stringEncoding = SBStringEncodingUTF32;
|
||||||
|
codepoint_sequence.stringBuffer = (void*)str.c_str();
|
||||||
|
codepoint_sequence.stringLength = str.size();
|
||||||
|
|
||||||
|
// Extract the first bidirectional paragraph
|
||||||
|
SBAlgorithmRef bidi_algorithm = SBAlgorithmCreate(&codepoint_sequence);
|
||||||
|
SBParagraphRef first_paragraph = SBAlgorithmCreateParagraph(bidi_algorithm,
|
||||||
|
0, (int32_t)-1, SBLevelDefaultLTR);
|
||||||
|
SBUInteger paragraph_length = SBParagraphGetLength(first_paragraph);
|
||||||
|
|
||||||
|
// Create a line consisting of whole paragraph and get its runs
|
||||||
|
SBLineRef paragraph_line = SBParagraphCreateLine(first_paragraph, 0,
|
||||||
|
paragraph_length);
|
||||||
|
SBUInteger run_count = SBLineGetRunCount(paragraph_line);
|
||||||
|
const SBRun* run_array = SBLineGetRunsPtr(paragraph_line);
|
||||||
|
|
||||||
|
std::vector<bool> rtl_line, rtl_char;
|
||||||
|
rtl_line.resize(str.size(), false);
|
||||||
|
rtl_char.resize(str.size(), false);
|
||||||
|
for (SBUInteger run = 0; run < run_count; run++)
|
||||||
{
|
{
|
||||||
Log::error("FontManager", "Failed to raqm_create.");
|
|
||||||
gls.clear();
|
|
||||||
if (line_data)
|
|
||||||
line_data->clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int length = (int)str.size();
|
|
||||||
const uint32_t* string_array = (const uint32_t*)str.c_str();
|
|
||||||
|
|
||||||
if (!raqm_set_text(rq, string_array, length))
|
|
||||||
{
|
|
||||||
Log::error("FontManager", "Failed to raqm_set_text.");
|
|
||||||
raqm_destroy(rq);
|
|
||||||
gls.clear();
|
|
||||||
if (line_data)
|
|
||||||
line_data->clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FT_Face prev_face = NULL;
|
FT_Face prev_face = NULL;
|
||||||
for (int i = 0; i < length; i++)
|
std::vector<std::pair<FT_Face, SBInteger> > face_for_shape;
|
||||||
|
SBInteger run_length = run_array[run].length;
|
||||||
|
hb_buffer_flags_t hb_buffer_flags = static_cast<hb_buffer_flags_t>(
|
||||||
|
HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT);
|
||||||
|
hb_direction_t direction = run_array[run].level % 2 == 0 ?
|
||||||
|
HB_DIRECTION_LTR : HB_DIRECTION_RTL;
|
||||||
|
for (SBInteger l = 0; l < run_length; l++)
|
||||||
{
|
{
|
||||||
|
SBInteger i = l + run_array[run].offset;
|
||||||
|
rtl_char[i] = direction == HB_DIRECTION_RTL;
|
||||||
|
// Use the first character in line to determine paragraph
|
||||||
|
// direction
|
||||||
|
if (i == 0 && direction == HB_DIRECTION_RTL)
|
||||||
|
std::fill(rtl_line.begin(), rtl_line.end(), true);
|
||||||
FT_Face cur_face = m_faces.front();
|
FT_Face cur_face = m_faces.front();
|
||||||
bool override_face = false;
|
bool override_face = false;
|
||||||
if (prev_face != NULL && i != 0)
|
if (prev_face != NULL && i != 0)
|
||||||
@ -376,15 +372,16 @@ void FontManager::shape(const std::u32string& text,
|
|||||||
(str[i] == U'.' || str[i] == U'!' || str[i] == U':')))
|
(str[i] == U'.' || str[i] == U'!' || str[i] == U':')))
|
||||||
{
|
{
|
||||||
// For inherited script (like punctation with arabic or
|
// For inherited script (like punctation with arabic or
|
||||||
// join marks), try to use the previous face so it is not
|
// join marks), try to use the previous face so it is
|
||||||
// hb_shape separately
|
// not hb_shape separately
|
||||||
cur_face = prev_face;
|
cur_face = prev_face;
|
||||||
override_face = true;
|
override_face = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FT_Face emoji_face = m_faces.size() > 1 ? m_faces[1] : NULL;
|
FT_Face emoji_face = m_faces.size() > 1 ? m_faces[1] : NULL;
|
||||||
if (m_has_color_emoji && !override_face &&
|
if (m_has_color_emoji && !override_face &&
|
||||||
length > 1 && i < length - 1 &&
|
run_length > 1 &&
|
||||||
|
i < (SBInteger)(run_length + run_array[run].offset - 1) &&
|
||||||
emoji_face != NULL && str[i + 1] == 0xfe0f)
|
emoji_face != NULL && str[i + 1] == 0xfe0f)
|
||||||
{
|
{
|
||||||
// Rule for variation selector-16 (emoji presentation)
|
// Rule for variation selector-16 (emoji presentation)
|
||||||
@ -415,31 +412,94 @@ void FontManager::shape(const std::u32string& text,
|
|||||||
checkFTError(FT_Set_Pixel_Sizes(cur_face, 0,
|
checkFTError(FT_Set_Pixel_Sizes(cur_face, 0,
|
||||||
m_shaping_dpi), "setting DPI");
|
m_shaping_dpi), "setting DPI");
|
||||||
}
|
}
|
||||||
if (!raqm_set_freetype_face_range(rq, cur_face, i, 1))
|
face_for_shape.emplace_back(cur_face, i);
|
||||||
{
|
|
||||||
Log::error("FontManager",
|
|
||||||
"Failed to raqm_set_freetype_face_range.");
|
|
||||||
raqm_destroy(rq);
|
|
||||||
gls.clear();
|
|
||||||
if (line_data)
|
|
||||||
line_data->clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (face_for_shape.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
if (raqm_layout(rq))
|
// Compare if different hb_font should be used in this run
|
||||||
|
auto shape_compare = face_for_shape.front();
|
||||||
|
auto it = face_for_shape.begin();
|
||||||
|
|
||||||
|
// Depend on direction push front or back in this vector
|
||||||
|
std::vector<ShapeGlyph> run_glyphs;
|
||||||
|
while (it != face_for_shape.end())
|
||||||
|
{
|
||||||
|
const std::pair<FT_Face, SBInteger>& cur_compare = *it;
|
||||||
|
if (cur_compare.first != shape_compare.first)
|
||||||
|
{
|
||||||
|
hb_buffer_reset(m_hb_buffer);
|
||||||
|
int offset = shape_compare.second;
|
||||||
|
int length = cur_compare.second - offset;
|
||||||
|
FT_Face ft_face = shape_compare.first;
|
||||||
|
hb_buffer_add_utf32(m_hb_buffer,
|
||||||
|
(uint32_t*)(str.c_str() + offset), length, 0, -1);
|
||||||
|
hb_buffer_guess_segment_properties(m_hb_buffer);
|
||||||
|
hb_buffer_set_direction(m_hb_buffer, direction);
|
||||||
|
hb_buffer_set_flags(m_hb_buffer, hb_buffer_flags);
|
||||||
|
hb_shape(
|
||||||
|
m_hb_fonts[m_ft_faces_to_index[ft_face]],
|
||||||
|
m_hb_buffer, NULL, 0);
|
||||||
|
std::vector<ShapeGlyph> shaped_glyphs;
|
||||||
|
fill_shape_glyph(shaped_glyphs, m_hb_buffer, offset,
|
||||||
|
ft_face);
|
||||||
|
if (direction == HB_DIRECTION_LTR)
|
||||||
|
{
|
||||||
|
run_glyphs.insert(run_glyphs.end(),
|
||||||
|
shaped_glyphs.begin(), shaped_glyphs.end());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
run_glyphs.insert(run_glyphs.begin(),
|
||||||
|
shaped_glyphs.begin(), shaped_glyphs.end());
|
||||||
|
}
|
||||||
|
shape_compare = cur_compare;
|
||||||
|
it = face_for_shape.erase(face_for_shape.begin(), it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
// Remaining pair if they use the same face
|
||||||
|
if (!face_for_shape.empty())
|
||||||
|
{
|
||||||
|
hb_buffer_reset(m_hb_buffer);
|
||||||
|
int offset = face_for_shape.front().second;
|
||||||
|
int length = face_for_shape.back().second - offset + 1;
|
||||||
|
FT_Face ft_face = face_for_shape.front().first;
|
||||||
|
hb_buffer_add_utf32(m_hb_buffer,
|
||||||
|
(uint32_t*)(str.c_str() + offset), length, 0, -1);
|
||||||
|
hb_buffer_guess_segment_properties(m_hb_buffer);
|
||||||
|
hb_buffer_set_direction(m_hb_buffer, direction);
|
||||||
|
hb_buffer_set_flags(m_hb_buffer, hb_buffer_flags);
|
||||||
|
hb_shape(m_hb_fonts[m_ft_faces_to_index[ft_face]],
|
||||||
|
m_hb_buffer, NULL, 0);
|
||||||
|
std::vector<ShapeGlyph> shaped_glyphs;
|
||||||
|
fill_shape_glyph(shaped_glyphs, m_hb_buffer, offset, ft_face);
|
||||||
|
if (direction == HB_DIRECTION_LTR)
|
||||||
|
{
|
||||||
|
run_glyphs.insert(run_glyphs.end(),
|
||||||
|
shaped_glyphs.begin(), shaped_glyphs.end());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
run_glyphs.insert(run_glyphs.begin(),
|
||||||
|
shaped_glyphs.begin(), shaped_glyphs.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glyphs.insert(glyphs.end(), run_glyphs.begin(), run_glyphs.end());
|
||||||
|
}
|
||||||
|
SBLineRelease(paragraph_line);
|
||||||
|
SBParagraphRelease(first_paragraph);
|
||||||
|
SBAlgorithmRelease(bidi_algorithm);
|
||||||
|
|
||||||
|
if (!glyphs.empty())
|
||||||
{
|
{
|
||||||
std::vector<gui::GlyphLayout> cur_line;
|
std::vector<gui::GlyphLayout> cur_line;
|
||||||
std::vector<bool> rtl_line, rtl_char, breakable;
|
std::vector<bool> breakable;
|
||||||
rtl_line.resize(str.size(), false);
|
|
||||||
rtl_char.resize(str.size(), false);
|
|
||||||
breakable.resize(str.size(), false);
|
breakable.resize(str.size(), false);
|
||||||
LineBreakingRules::insertBreakMark(str, breakable);
|
LineBreakingRules::insertBreakMark(str, breakable);
|
||||||
translations->insertThaiBreakMark(str, breakable);
|
translations->insertThaiBreakMark(str, breakable);
|
||||||
RTLRules::insertRTLMark(str, rtl_line, rtl_char);
|
for (unsigned idx = 0; idx < glyphs.size(); idx++)
|
||||||
size_t count = 0;
|
|
||||||
raqm_glyph_t* glyphs = raqm_get_glyphs(rq, &count);
|
|
||||||
for (unsigned idx = 0; idx < (unsigned)count; idx++)
|
|
||||||
{
|
{
|
||||||
gui::GlyphLayout gl = { 0 };
|
gui::GlyphLayout gl = { 0 };
|
||||||
gl.index = glyphs[idx].index;
|
gl.index = glyphs[idx].index;
|
||||||
@ -502,19 +562,9 @@ void FontManager::shape(const std::u32string& text,
|
|||||||
gl.flags |= gui::GLF_BREAKABLE;
|
gl.flags |= gui::GLF_BREAKABLE;
|
||||||
}
|
}
|
||||||
gls.insert(gls.end(), cur_line.begin(), cur_line.end());
|
gls.insert(gls.end(), cur_line.begin(), cur_line.end());
|
||||||
raqm_destroy(rq);
|
|
||||||
if (line_data)
|
if (line_data)
|
||||||
line_data->push_back(str);
|
line_data->push_back(str);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Log::error("FontManager", "Failed to raqm_layout.");
|
|
||||||
raqm_destroy(rq);
|
|
||||||
gls.clear();
|
|
||||||
if (line_data)
|
|
||||||
line_data->clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} // shape
|
} // shape
|
||||||
|
|
||||||
@ -683,6 +733,16 @@ void FontManager::loadFonts()
|
|||||||
// Update inverse shaping from m_shaping_dpi
|
// Update inverse shaping from m_shaping_dpi
|
||||||
regular->setDPI();
|
regular->setDPI();
|
||||||
}
|
}
|
||||||
|
for (FT_Face face : normal_ttf)
|
||||||
|
{
|
||||||
|
if (!FT_HAS_COLOR(face) ||
|
||||||
|
(FT_HAS_COLOR(face) && face->num_fixed_sizes == 0))
|
||||||
|
{
|
||||||
|
checkFTError(FT_Set_Pixel_Sizes(face, 0, m_shaping_dpi),
|
||||||
|
"setting DPI");
|
||||||
|
}
|
||||||
|
m_hb_fonts.push_back(hb_ft_font_create(face, NULL));
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_fonts.push_back(regular);
|
m_fonts.push_back(regular);
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
|
|
||||||
#ifndef SERVER_ONLY
|
#ifndef SERVER_ONLY
|
||||||
#include <ft2build.h>
|
#include <ft2build.h>
|
||||||
|
#include <harfbuzz/hb.h>
|
||||||
#include FT_FREETYPE_H
|
#include FT_FREETYPE_H
|
||||||
|
|
||||||
#include "irrString.h"
|
#include "irrString.h"
|
||||||
@ -78,6 +79,9 @@ private:
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
std::vector<FT_Face> loadTTF(const std::vector<std::string>& ttf_list);
|
std::vector<FT_Face> loadTTF(const std::vector<std::string>& ttf_list);
|
||||||
|
|
||||||
|
hb_buffer_t* m_hb_buffer;
|
||||||
|
|
||||||
|
std::vector<hb_font_t*> m_hb_fonts;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** Map type for each \ref FontWithFace with a index, save getting time in
|
/** Map type for each \ref FontWithFace with a index, save getting time in
|
||||||
|
Loading…
x
Reference in New Issue
Block a user