Use SheenBidi instead of FriBidi and libraqm

This commit is contained in:
Benau 2020-08-24 00:55:10 +08:00
parent 13db1b83c1
commit 369c346857
3 changed files with 214 additions and 151 deletions

View File

@ -228,13 +228,12 @@ Software used
Software used
- ogg
- vorbis
- fribidi
- SheenBidi
- blender
- wiiuse
- harfbuzz
Software used
- libraqm
- sqlite
- openssl
- mcpp

View File

@ -30,9 +30,11 @@
#include "utils/translation.hpp"
#ifndef SERVER_ONLY
#include <fribidi/fribidi.h>
#include <harfbuzz/hb.h>
#include <raqm.h>
#include <harfbuzz/hb-ft.h>
extern "C"
{
#include <SheenBidi.h>
}
#endif
FontManager *font_manager = NULL;
@ -46,9 +48,11 @@ FontManager::FontManager()
m_ft_library = NULL;
m_digit_face = NULL;
m_shaping_dpi = 128;
m_hb_buffer = NULL;
if (GUIEngine::isNoGraphics())
return;
m_hb_buffer = hb_buffer_create();
checkFTError(FT_Init_FreeType(&m_ft_library), "loading freetype library");
#endif
} // FontManager
@ -66,6 +70,9 @@ FontManager::~FontManager()
if (GUIEngine::isNoGraphics())
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++)
checkFTError(FT_Done_Face(m_faces[i]), "removing faces for shaping");
if (m_digit_face != NULL)
@ -253,60 +260,37 @@ namespace LineBreakingRules
} // insertBreakMark
} // 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. */
void FontManager::shape(const std::u32string& text,
std::vector<irr::gui::GlyphLayout>& gls,
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
if (text.empty() || m_faces.empty())
return;
@ -319,6 +303,7 @@ void FontManager::shape(const std::u32string& text,
for (unsigned l = 0; l < lines.size(); l++)
{
std::vector<ShapeGlyph> glyphs;
if (l != 0)
{
gui::GlyphLayout gl = { 0 };
@ -336,110 +321,185 @@ void FontManager::shape(const std::u32string& text,
continue;
}
raqm_t* rq = raqm_create();
if (!rq)
{
Log::error("FontManager", "Failed to raqm_create.");
gls.clear();
if (line_data)
line_data->clear();
return;
}
SBCodepointSequence codepoint_sequence;
codepoint_sequence.stringEncoding = SBStringEncodingUTF32;
codepoint_sequence.stringBuffer = (void*)str.c_str();
codepoint_sequence.stringLength = str.size();
const int length = (int)str.size();
const uint32_t* string_array = (const uint32_t*)str.c_str();
// 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);
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;
}
// 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);
FT_Face prev_face = NULL;
for (int i = 0; i < length; i++)
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++)
{
FT_Face cur_face = m_faces.front();
bool override_face = false;
if (prev_face != NULL && i != 0)
FT_Face prev_face = NULL;
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++)
{
hb_script_t prev_script = hb_unicode_script(
hb_unicode_funcs_get_default(), str[i - 1]);
hb_script_t cur_script = hb_unicode_script(
hb_unicode_funcs_get_default(), str[i]);
if (cur_script == HB_SCRIPT_INHERITED ||
(prev_script == HB_SCRIPT_ARABIC &&
// Those exists in the default arabic font
(str[i] == U'.' || str[i] == U'!' || str[i] == U':')))
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();
bool override_face = false;
if (prev_face != NULL && i != 0)
{
// For inherited script (like punctation with arabic or
// join marks), try to use the previous face so it is not
// hb_shape separately
cur_face = prev_face;
override_face = true;
}
}
FT_Face emoji_face = m_faces.size() > 1 ? m_faces[1] : NULL;
if (m_has_color_emoji && !override_face &&
length > 1 && i < length - 1 &&
emoji_face != NULL && str[i + 1] == 0xfe0f)
{
// Rule for variation selector-16 (emoji presentation)
// It is used in for example Keycap Digit One
// (U+31, U+FE0F, U+20E3)
cur_face = emoji_face;
override_face = true;
}
if (!override_face)
{
for (unsigned j = 0; j < m_faces.size(); j++)
{
unsigned glyph_index =
FT_Get_Char_Index(m_faces[j], str[i]);
if (glyph_index > 0)
hb_script_t prev_script = hb_unicode_script(
hb_unicode_funcs_get_default(), str[i - 1]);
hb_script_t cur_script = hb_unicode_script(
hb_unicode_funcs_get_default(), str[i]);
if (cur_script == HB_SCRIPT_INHERITED ||
(prev_script == HB_SCRIPT_ARABIC &&
// Those exists in the default arabic font
(str[i] == U'.' || str[i] == U'!' || str[i] == U':')))
{
cur_face = m_faces[j];
break;
// For inherited script (like punctation with arabic or
// join marks), try to use the previous face so it is
// not hb_shape separately
cur_face = prev_face;
override_face = true;
}
}
FT_Face emoji_face = m_faces.size() > 1 ? m_faces[1] : NULL;
if (m_has_color_emoji && !override_face &&
run_length > 1 &&
i < (SBInteger)(run_length + run_array[run].offset - 1) &&
emoji_face != NULL && str[i + 1] == 0xfe0f)
{
// Rule for variation selector-16 (emoji presentation)
// It is used in for example Keycap Digit One
// (U+31, U+FE0F, U+20E3)
cur_face = emoji_face;
override_face = true;
}
if (!override_face)
{
for (unsigned j = 0; j < m_faces.size(); j++)
{
unsigned glyph_index =
FT_Get_Char_Index(m_faces[j], str[i]);
if (glyph_index > 0)
{
cur_face = m_faces[j];
break;
}
}
}
prev_face = cur_face;
if (!FT_HAS_COLOR(cur_face) ||
(FT_HAS_COLOR(cur_face) && cur_face->num_fixed_sizes == 0))
{
// Handle color emoji with CPAL / COLR tables
// (num_fixed_sizes == 0)
checkFTError(FT_Set_Pixel_Sizes(cur_face, 0,
m_shaping_dpi), "setting DPI");
}
face_for_shape.emplace_back(cur_face, i);
}
prev_face = cur_face;
if (!FT_HAS_COLOR(cur_face) ||
(FT_HAS_COLOR(cur_face) && cur_face->num_fixed_sizes == 0))
{
// Handle color emoji with CPAL / COLR tables
// (num_fixed_sizes == 0)
checkFTError(FT_Set_Pixel_Sizes(cur_face, 0,
m_shaping_dpi), "setting DPI");
}
if (!raqm_set_freetype_face_range(rq, cur_face, i, 1))
{
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<bool> rtl_line, rtl_char, breakable;
rtl_line.resize(str.size(), false);
rtl_char.resize(str.size(), false);
std::vector<bool> breakable;
breakable.resize(str.size(), false);
LineBreakingRules::insertBreakMark(str, breakable);
translations->insertThaiBreakMark(str, breakable);
RTLRules::insertRTLMark(str, rtl_line, rtl_char);
size_t count = 0;
raqm_glyph_t* glyphs = raqm_get_glyphs(rq, &count);
for (unsigned idx = 0; idx < (unsigned)count; idx++)
for (unsigned idx = 0; idx < glyphs.size(); idx++)
{
gui::GlyphLayout gl = { 0 };
gl.index = glyphs[idx].index;
@ -502,19 +562,9 @@ void FontManager::shape(const std::u32string& text,
gl.flags |= gui::GLF_BREAKABLE;
}
gls.insert(gls.end(), cur_line.begin(), cur_line.end());
raqm_destroy(rq);
if (line_data)
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
@ -683,6 +733,16 @@ void FontManager::loadFonts()
// Update inverse shaping from m_shaping_dpi
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
m_fonts.push_back(regular);

View File

@ -35,6 +35,7 @@
#ifndef SERVER_ONLY
#include <ft2build.h>
#include <harfbuzz/hb.h>
#include FT_FREETYPE_H
#include "irrString.h"
@ -78,6 +79,9 @@ private:
// ------------------------------------------------------------------------
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
/** Map type for each \ref FontWithFace with a index, save getting time in