|
|
@@ -37,10 +37,11 @@
|
|
|
#include <hb-ft.h>
|
|
|
#include <hb.h>
|
|
|
#include <numeric>
|
|
|
+#include <utility>
|
|
|
|
|
|
-static bool IsASCIIControlCharacter(Character c)
|
|
|
+static bool IsControlCharacter(Character c)
|
|
|
{
|
|
|
- return (char32_t)c < ' ';
|
|
|
+ return (char32_t)c < U' ' || ((char32_t)c >= U'\x7F' && (char32_t)c <= U'\x9F');
|
|
|
}
|
|
|
|
|
|
FontFaceHandleHarfBuzz::FontFaceHandleHarfBuzz()
|
|
|
@@ -95,6 +96,11 @@ const FallbackFontGlyphMap& FontFaceHandleHarfBuzz::GetFallbackGlyphs() const
|
|
|
return fallback_glyphs;
|
|
|
}
|
|
|
|
|
|
+const FallbackFontClusterGlyphsMap& FontFaceHandleHarfBuzz::GetFallbackClusterGlyphs() const
|
|
|
+{
|
|
|
+ return fallback_cluster_glyphs;
|
|
|
+}
|
|
|
+
|
|
|
int FontFaceHandleHarfBuzz::GetStringWidth(StringView string, const TextShapingContext& text_shaping_context,
|
|
|
const LanguageDataMap& registered_languages, Character /*prior_character*/)
|
|
|
{
|
|
|
@@ -103,7 +109,7 @@ int FontFaceHandleHarfBuzz::GetStringWidth(StringView string, const TextShapingC
|
|
|
// Apply text shaping.
|
|
|
hb_buffer_t* shaping_buffer = hb_buffer_create();
|
|
|
RMLUI_ASSERT(shaping_buffer != nullptr);
|
|
|
- ConfigureTextShapingBuffer(shaping_buffer, string, text_shaping_context, registered_languages);
|
|
|
+ ConfigureTextShapingBuffer(shaping_buffer, string, text_shaping_context, registered_languages, nullptr);
|
|
|
hb_buffer_add_utf8(shaping_buffer, string.begin(), (int)string.size(), 0, (int)string.size());
|
|
|
hb_shape(hb_font, shaping_buffer, nullptr, 0);
|
|
|
|
|
|
@@ -111,26 +117,64 @@ int FontFaceHandleHarfBuzz::GetStringWidth(StringView string, const TextShapingC
|
|
|
hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(shaping_buffer, &glyph_count);
|
|
|
hb_glyph_position_t* glyph_positions = hb_buffer_get_glyph_positions(shaping_buffer, &glyph_count);
|
|
|
|
|
|
+ Queue<Pair<FontGlyphIndex, const FontGlyph*>> glyph_queue;
|
|
|
+
|
|
|
for (int g = 0; g < (int)glyph_count; ++g)
|
|
|
{
|
|
|
- // Don't render control characters.
|
|
|
Character character = Rml::StringUtilities::ToCharacter(string.begin() + glyph_info[g].cluster, string.end());
|
|
|
- if (IsASCIIControlCharacter(character))
|
|
|
- continue;
|
|
|
|
|
|
- FontGlyphIndex glyph_index = glyph_info[g].codepoint;
|
|
|
- const FontGlyph* glyph = GetOrAppendGlyph(glyph_index, character);
|
|
|
- if (!glyph)
|
|
|
+ // Don't render control characters.
|
|
|
+ if (IsControlCharacter(character))
|
|
|
continue;
|
|
|
|
|
|
- // Adjust the cursor for this character's advance.
|
|
|
- if (glyph_index != 0)
|
|
|
- width += glyph_positions[g].x_advance >> 6;
|
|
|
- else
|
|
|
- // Use the unshaped advance for unsupported characters.
|
|
|
- width += glyph->advance;
|
|
|
+ const FontGlyphIndex glyph_index = glyph_info[g].codepoint;
|
|
|
+ int extra_glyph_index_offset = 0;
|
|
|
+
|
|
|
+ if (glyph_index == 0)
|
|
|
+ {
|
|
|
+ // Check to see if the glyph is the start of an unsupported multi-character cluster.
|
|
|
+ int cluster_codepoint_count = 0;
|
|
|
+ StringView cluster_string = GetCurrentClusterString(glyph_info, glyph_count, g, character, string, cluster_codepoint_count);
|
|
|
+
|
|
|
+ if (cluster_codepoint_count > 1)
|
|
|
+ {
|
|
|
+ // Unsupported cluster detected; use fallback cluster glyph if one is available.
|
|
|
+ const Vector<FontClusterGlyphData>* cluster_glyphs =
|
|
|
+ GetOrAppendFallbackClusterGlyphs(cluster_string, text_shaping_context, registered_languages);
|
|
|
+
|
|
|
+ if (cluster_glyphs)
|
|
|
+ {
|
|
|
+ extra_glyph_index_offset = cluster_codepoint_count - 1;
|
|
|
+ for (const auto& cluster_glyph : *cluster_glyphs)
|
|
|
+ glyph_queue.emplace(cluster_glyph.glyph_index, &cluster_glyph.glyph_data.bitmap);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- width += (int)text_shaping_context.letter_spacing;
|
|
|
+ if (glyph_queue.empty())
|
|
|
+ {
|
|
|
+ const FontGlyph* glyph = GetOrAppendGlyph(glyph_index, character);
|
|
|
+ if (glyph)
|
|
|
+ glyph_queue.emplace(glyph_index, glyph);
|
|
|
+ }
|
|
|
+
|
|
|
+ while (!glyph_queue.empty())
|
|
|
+ {
|
|
|
+ auto& glyph_pair = glyph_queue.front();
|
|
|
+
|
|
|
+ // Adjust the cursor for this character's advance.
|
|
|
+ if (glyph_info[g].codepoint != 0)
|
|
|
+ width += glyph_positions[g].x_advance >> 6;
|
|
|
+ else
|
|
|
+ // Use the unshaped advance for unsupported characters.
|
|
|
+ width += glyph_pair.second->advance;
|
|
|
+
|
|
|
+ width += (int)text_shaping_context.letter_spacing;
|
|
|
+
|
|
|
+ glyph_queue.pop();
|
|
|
+ }
|
|
|
+
|
|
|
+ g += extra_glyph_index_offset;
|
|
|
}
|
|
|
|
|
|
hb_buffer_destroy(shaping_buffer);
|
|
|
@@ -218,7 +262,8 @@ bool FontFaceHandleHarfBuzz::GenerateLayerTexture(Vector<byte>& texture_data, Ve
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- return it->layer->GenerateTexture(texture_data, texture_dimensions, texture_id, glyphs, fallback_glyphs);
|
|
|
+ return it->layer->GenerateTexture(texture_data, texture_dimensions, texture_id,
|
|
|
+ FontGlyphMaps{&glyphs, &fallback_glyphs, &fallback_cluster_glyphs_lookup});
|
|
|
}
|
|
|
|
|
|
int FontFaceHandleHarfBuzz::GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, StringView string, const Vector2f position,
|
|
|
@@ -269,7 +314,7 @@ int FontFaceHandleHarfBuzz::GenerateString(RenderManager& render_manager, Textur
|
|
|
|
|
|
// Set up and apply text shaping.
|
|
|
hb_buffer_clear_contents(shaping_buffer);
|
|
|
- ConfigureTextShapingBuffer(shaping_buffer, string, text_shaping_context, registered_languages);
|
|
|
+ ConfigureTextShapingBuffer(shaping_buffer, string, text_shaping_context, registered_languages, nullptr);
|
|
|
hb_buffer_add_utf8(shaping_buffer, string.begin(), (int)string.size(), 0, (int)string.size());
|
|
|
hb_shape(hb_font, shaping_buffer, nullptr, 0);
|
|
|
|
|
|
@@ -280,36 +325,76 @@ int FontFaceHandleHarfBuzz::GenerateString(RenderManager& render_manager, Textur
|
|
|
mesh_list[geometry_index].mesh.indices.reserve(string.size() * 6);
|
|
|
mesh_list[geometry_index].mesh.vertices.reserve(string.size() * 4);
|
|
|
|
|
|
+ Queue<Pair<FontGlyphIndex, FontGlyphReference>> glyph_queue;
|
|
|
+
|
|
|
for (int g = 0; g < (int)glyph_count; ++g)
|
|
|
{
|
|
|
Character character = Rml::StringUtilities::ToCharacter(string.begin() + glyph_info[g].cluster, string.end());
|
|
|
|
|
|
// Don't render control characters.
|
|
|
- if (IsASCIIControlCharacter(character))
|
|
|
+ if (IsControlCharacter(character))
|
|
|
continue;
|
|
|
|
|
|
- FontGlyphIndex glyph_index = glyph_info[g].codepoint;
|
|
|
- const FontGlyph* glyph = GetOrAppendGlyph(glyph_index, character);
|
|
|
- if (!glyph)
|
|
|
- continue;
|
|
|
+ const FontGlyphIndex glyph_index = glyph_info[g].codepoint;
|
|
|
+ int extra_glyph_index_offset = 0;
|
|
|
+ bool is_cluster = false;
|
|
|
|
|
|
- ColourbPremultiplied glyph_color = layer_colour;
|
|
|
- // Use white vertex colors on RGB glyphs.
|
|
|
- if (layer == base_layer && glyph->color_format == ColorFormat::RGBA8)
|
|
|
- glyph_color = ColourbPremultiplied(layer_colour.alpha, layer_colour.alpha);
|
|
|
+ if (glyph_index == 0)
|
|
|
+ {
|
|
|
+ // Check to see if the glyph is the start of an unsupported multi-character cluster.
|
|
|
+ int cluster_codepoint_count = 0;
|
|
|
+ StringView cluster_string = GetCurrentClusterString(glyph_info, glyph_count, g, character, string, cluster_codepoint_count);
|
|
|
+
|
|
|
+ if (cluster_codepoint_count > 1)
|
|
|
+ {
|
|
|
+ // Unsupported cluster detected; use fallback cluster glyph if one is available.
|
|
|
+ const Vector<FontClusterGlyphData>* cluster_glyphs = GetOrAppendFallbackClusterGlyphs(cluster_string, text_shaping_context, registered_languages);
|
|
|
+
|
|
|
+ if (cluster_glyphs)
|
|
|
+ {
|
|
|
+ extra_glyph_index_offset = cluster_codepoint_count - 1;
|
|
|
+ is_cluster = true;
|
|
|
+
|
|
|
+ for (const auto& cluster_glyph : *cluster_glyphs)
|
|
|
+ glyph_queue.emplace(cluster_glyph.glyph_index, FontGlyphReference{&cluster_glyph.glyph_data.bitmap, cluster_glyph.glyph_data.character});
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- Vector2f glyph_offset = {float(glyph_positions[g].x_offset >> 6), float(glyph_positions[g].y_offset >> 6)};
|
|
|
- Vector2f glyph_geometry_position = Vector2f{position.x + line_width, position.y} + glyph_offset;
|
|
|
- layer->GenerateGeometry(&mesh_list[geometry_index], glyph_index, character, glyph_geometry_position, glyph_color);
|
|
|
+ if (glyph_queue.empty())
|
|
|
+ {
|
|
|
+ const FontGlyph* glyph = GetOrAppendGlyph(glyph_index, character);
|
|
|
+ if (glyph)
|
|
|
+ glyph_queue.emplace(glyph_index, FontGlyphReference{glyph, character});
|
|
|
+ }
|
|
|
|
|
|
- // Adjust the cursor for this character's advance.
|
|
|
- if (glyph_index != 0)
|
|
|
- line_width += glyph_positions[g].x_advance >> 6;
|
|
|
- else
|
|
|
- // Use the unshaped advance for unsupported characters.
|
|
|
- line_width += glyph->advance;
|
|
|
+ while (!glyph_queue.empty())
|
|
|
+ {
|
|
|
+ auto& glyph_pair = glyph_queue.front();
|
|
|
|
|
|
- line_width += (int)text_shaping_context.letter_spacing;
|
|
|
+ ColourbPremultiplied glyph_color = layer_colour;
|
|
|
+ // Use white vertex colors on RGB glyphs.
|
|
|
+ if (layer == base_layer && glyph_pair.second.bitmap->color_format == ColorFormat::RGBA8)
|
|
|
+ glyph_color = ColourbPremultiplied(layer_colour.alpha, layer_colour.alpha);
|
|
|
+
|
|
|
+ Vector2f glyph_offset = {0.0f, 0.0f};
|
|
|
+ glyph_offset += Vector2f{float(glyph_positions[g].x_offset >> 6), float(glyph_positions[g].y_offset >> 6)};
|
|
|
+ Vector2f glyph_geometry_position = Vector2f{position.x + line_width, position.y} + glyph_offset;
|
|
|
+ layer->GenerateGeometry(&mesh_list[geometry_index], glyph_pair.first, glyph_pair.second.character, is_cluster, glyph_geometry_position, glyph_color);
|
|
|
+
|
|
|
+ // Adjust the cursor for this character's advance.
|
|
|
+ if (glyph_info[g].codepoint != 0)
|
|
|
+ line_width += glyph_positions[g].x_advance >> 6;
|
|
|
+ else
|
|
|
+ // Use the unshaped advance for unsupported characters.
|
|
|
+ line_width += glyph_pair.second.bitmap->advance;
|
|
|
+
|
|
|
+ line_width += (int)text_shaping_context.letter_spacing;
|
|
|
+
|
|
|
+ glyph_queue.pop();
|
|
|
+ }
|
|
|
+
|
|
|
+ g += extra_glyph_index_offset;
|
|
|
}
|
|
|
|
|
|
geometry_index += num_textures;
|
|
|
@@ -355,7 +440,7 @@ bool FontFaceHandleHarfBuzz::AppendGlyph(FontGlyphIndex glyph_index, Character c
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
-bool FontFaceHandleHarfBuzz::AppendFallbackGlyph(Character character)
|
|
|
+bool FontFaceHandleHarfBuzz::AppendFallbackGlyph(Character& character)
|
|
|
{
|
|
|
const int num_fallback_faces = FontProvider::CountFallbackFontFaces();
|
|
|
for (int i = 0; i < num_fallback_faces; i++)
|
|
|
@@ -416,12 +501,14 @@ const FontGlyph* FontFaceHandleHarfBuzz::GetOrAppendGlyph(FontGlyphIndex glyph_i
|
|
|
|
|
|
if (glyph_index == 0)
|
|
|
character = Character::Replacement;
|
|
|
+ else if (character != glyph_location->second.character)
|
|
|
+ character = glyph_location->second.character;
|
|
|
|
|
|
const FontGlyph* glyph = &glyph_location->second.bitmap;
|
|
|
return glyph;
|
|
|
}
|
|
|
|
|
|
-const FontGlyph* FontFaceHandleHarfBuzz::GetOrAppendFallbackGlyph(Character character)
|
|
|
+const FontGlyph* FontFaceHandleHarfBuzz::GetOrAppendFallbackGlyph(Character& character)
|
|
|
{
|
|
|
auto fallback_glyph_location = fallback_glyphs.find(character);
|
|
|
if (fallback_glyph_location != fallback_glyphs.cend())
|
|
|
@@ -447,6 +534,115 @@ const FontGlyph* FontFaceHandleHarfBuzz::GetOrAppendFallbackGlyph(Character char
|
|
|
return fallback_glyph;
|
|
|
}
|
|
|
|
|
|
+bool FontFaceHandleHarfBuzz::AppendFallbackClusterGlyphs(StringView cluster, const TextShapingContext& text_shaping_context,
|
|
|
+ const LanguageDataMap& registered_languages)
|
|
|
+{
|
|
|
+ hb_buffer_t* shaping_buffer = hb_buffer_create();
|
|
|
+ RMLUI_ASSERT(shaping_buffer != nullptr);
|
|
|
+
|
|
|
+ TextFlowDirection text_direction = TextFlowDirection::LeftToRight;
|
|
|
+
|
|
|
+ // Iterate through all available fallback font faces.
|
|
|
+ const int num_fallback_faces = FontProvider::CountFallbackFontFaces();
|
|
|
+ for (int i = 0; i < num_fallback_faces; i++)
|
|
|
+ {
|
|
|
+ FontFaceHandleHarfBuzz* fallback_face = FontProvider::GetFallbackFontFace(i, metrics.size);
|
|
|
+ if (!fallback_face || fallback_face == this)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ // Insert the cluster into a shaping buffer and perform text shaping.
|
|
|
+ hb_buffer_clear_contents(shaping_buffer);
|
|
|
+ ConfigureTextShapingBuffer(shaping_buffer, cluster, text_shaping_context, registered_languages, &text_direction);
|
|
|
+ hb_buffer_add_utf8(shaping_buffer, cluster.begin(), (int)cluster.size(), 0, (int)cluster.size());
|
|
|
+ hb_shape(fallback_face->hb_font, shaping_buffer, nullptr, 0);
|
|
|
+
|
|
|
+ unsigned int glyph_count = 0;
|
|
|
+ hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(shaping_buffer, &glyph_count);
|
|
|
+ if (glyph_count == 0)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ Vector<FontClusterGlyphData> cluster_glyphs;
|
|
|
+ cluster_glyphs.reserve((size_t)glyph_count);
|
|
|
+
|
|
|
+ int glyph_info_index_offset = text_direction == TextFlowDirection::RightToLeft ? (int)glyph_count - 1 : 0;
|
|
|
+ int cluster_string_offset = 0;
|
|
|
+ bool has_nonzero_codepoint = false;
|
|
|
+
|
|
|
+ // Create the cluster glyphs.
|
|
|
+ for (int g = 0; g < (int)glyph_count; ++g)
|
|
|
+ {
|
|
|
+ int glyph_info_index = g + glyph_info_index_offset;
|
|
|
+ RMLUI_ASSERT(glyph_info_index < (int)glyph_count);
|
|
|
+
|
|
|
+ // Reverse the order of the glyphs in right-to-left text.
|
|
|
+ if (text_direction == TextFlowDirection::RightToLeft)
|
|
|
+ glyph_info_index_offset -= 2;
|
|
|
+
|
|
|
+ if (!has_nonzero_codepoint && glyph_info[glyph_info_index].codepoint != 0)
|
|
|
+ has_nonzero_codepoint = true;
|
|
|
+
|
|
|
+ Character character = Rml::StringUtilities::ToCharacter(cluster.begin() + cluster_string_offset, cluster.end());
|
|
|
+ const FontGlyph* glyph = fallback_face->GetOrAppendGlyph(glyph_info[glyph_info_index].codepoint, character, false);
|
|
|
+ if (glyph && glyph->bitmap_data)
|
|
|
+ cluster_glyphs.push_back(FontClusterGlyphData{glyph_info[glyph_info_index].codepoint, FontGlyphData{glyph->WeakCopy(), character}});
|
|
|
+
|
|
|
+ cluster_string_offset += (int)Rml::StringUtilities::BytesUTF8(character);
|
|
|
+ RMLUI_ASSERT(cluster_string_offset <= (int)cluster.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (cluster_glyphs.empty() || !has_nonzero_codepoint)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ // Insert the cluster glyphs into our own set of fallback cluster glyphs.
|
|
|
+ auto pair = fallback_cluster_glyphs.emplace(cluster, std::move(cluster_glyphs));
|
|
|
+ if (pair.second)
|
|
|
+ {
|
|
|
+ is_layers_dirty = true;
|
|
|
+
|
|
|
+ // Populate quick-lookup glyph map to glyph search times during rendering.
|
|
|
+ for (const auto& cluster_glyph : pair.first->second)
|
|
|
+ {
|
|
|
+ uint64_t cluster_glyph_id = GetFallbackFontClusterGlyphLookupID(cluster_glyph.glyph_index, cluster_glyph.glyph_data.character);
|
|
|
+ fallback_cluster_glyphs_lookup.emplace(cluster_glyph_id, &cluster_glyph.glyph_data.bitmap);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+const Vector<FontClusterGlyphData>* FontFaceHandleHarfBuzz::GetOrAppendFallbackClusterGlyphs(StringView cluster,
|
|
|
+ const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages)
|
|
|
+{
|
|
|
+ String cluster_string(cluster);
|
|
|
+ auto fallback_cluster_glyphs_location = fallback_cluster_glyphs.find(cluster_string);
|
|
|
+ if (fallback_cluster_glyphs_location != fallback_cluster_glyphs.cend())
|
|
|
+ {
|
|
|
+ return &fallback_cluster_glyphs_location->second;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool result = AppendFallbackClusterGlyphs(cluster, text_shaping_context, registered_languages);
|
|
|
+
|
|
|
+ if (result)
|
|
|
+ {
|
|
|
+ fallback_cluster_glyphs_location = fallback_cluster_glyphs.find(cluster_string);
|
|
|
+ if (fallback_cluster_glyphs_location == fallback_cluster_glyphs.cend())
|
|
|
+ {
|
|
|
+ RMLUI_ERROR;
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ is_layers_dirty = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ return nullptr;
|
|
|
+
|
|
|
+ const Vector<FontClusterGlyphData>* fallback_cluster_glyphs = &fallback_cluster_glyphs_location->second;
|
|
|
+ return fallback_cluster_glyphs;
|
|
|
+}
|
|
|
+
|
|
|
FontFaceLayer* FontFaceHandleHarfBuzz::GetOrCreateLayer(const SharedPtr<const FontEffect>& font_effect)
|
|
|
{
|
|
|
// Search for the font effect layer first, it may have been instanced before as part of a different configuration.
|
|
|
@@ -509,7 +705,7 @@ bool FontFaceHandleHarfBuzz::GenerateLayer(FontFaceLayer* layer)
|
|
|
}
|
|
|
|
|
|
void FontFaceHandleHarfBuzz::ConfigureTextShapingBuffer(hb_buffer_t* shaping_buffer, StringView string,
|
|
|
- const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages)
|
|
|
+ const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages, TextFlowDirection* determined_text_direction) const
|
|
|
{
|
|
|
// Set the buffer's language based on the value of the element's 'lang' attribute.
|
|
|
hb_buffer_set_language(shaping_buffer, hb_language_from_string(text_shaping_context.language.c_str(), -1));
|
|
|
@@ -534,6 +730,7 @@ void FontFaceHandleHarfBuzz::ConfigureTextShapingBuffer(hb_buffer_t* shaping_buf
|
|
|
hb_buffer_set_script(shaping_buffer, script);
|
|
|
|
|
|
// Set the buffer's text-flow direction based on the value of the element's 'dir' attribute.
|
|
|
+ hb_direction_t text_direction = HB_DIRECTION_LTR;
|
|
|
switch (text_shaping_context.text_direction)
|
|
|
{
|
|
|
case Rml::Style::Direction::Auto:
|
|
|
@@ -541,25 +738,28 @@ void FontFaceHandleHarfBuzz::ConfigureTextShapingBuffer(hb_buffer_t* shaping_buf
|
|
|
// Automatically determine the text-flow direction from the registered language.
|
|
|
switch (registered_language_location->second.text_flow_direction)
|
|
|
{
|
|
|
- case TextFlowDirection::LeftToRight: hb_buffer_set_direction(shaping_buffer, HB_DIRECTION_LTR); break;
|
|
|
- case TextFlowDirection::RightToLeft: hb_buffer_set_direction(shaping_buffer, HB_DIRECTION_RTL); break;
|
|
|
+ case TextFlowDirection::LeftToRight: text_direction = HB_DIRECTION_LTR; break;
|
|
|
+ case TextFlowDirection::RightToLeft: text_direction = HB_DIRECTION_RTL; break;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// Language not registered; determine best text-flow direction based on script.
|
|
|
- hb_direction_t text_direction = hb_script_get_horizontal_direction(script);
|
|
|
+ text_direction = hb_script_get_horizontal_direction(script);
|
|
|
if (text_direction == HB_DIRECTION_INVALID)
|
|
|
// Some scripts support both horizontal directions of text flow; default to left-to-right.
|
|
|
text_direction = HB_DIRECTION_LTR;
|
|
|
-
|
|
|
- hb_buffer_set_direction(shaping_buffer, text_direction);
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
- case Rml::Style::Direction::Ltr: hb_buffer_set_direction(shaping_buffer, HB_DIRECTION_LTR); break;
|
|
|
- case Rml::Style::Direction::Rtl: hb_buffer_set_direction(shaping_buffer, HB_DIRECTION_RTL); break;
|
|
|
+ case Rml::Style::Direction::Ltr: text_direction = HB_DIRECTION_LTR; break;
|
|
|
+ case Rml::Style::Direction::Rtl: text_direction = HB_DIRECTION_RTL; break;
|
|
|
}
|
|
|
|
|
|
+ RMLUI_ASSERT(text_direction == HB_DIRECTION_LTR || text_direction == HB_DIRECTION_RTL);
|
|
|
+ hb_buffer_set_direction(shaping_buffer, text_direction);
|
|
|
+ if (determined_text_direction)
|
|
|
+ *determined_text_direction = text_direction == HB_DIRECTION_LTR ? TextFlowDirection::LeftToRight : TextFlowDirection::RightToLeft;
|
|
|
+
|
|
|
// Set buffer flags for additional text-shaping configuration.
|
|
|
int buffer_flags = HB_BUFFER_FLAG_DEFAULT | HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT;
|
|
|
|
|
|
@@ -573,3 +773,25 @@ void FontFaceHandleHarfBuzz::ConfigureTextShapingBuffer(hb_buffer_t* shaping_buf
|
|
|
|
|
|
hb_buffer_set_flags(shaping_buffer, (hb_buffer_flags_t)buffer_flags);
|
|
|
}
|
|
|
+
|
|
|
+StringView FontFaceHandleHarfBuzz::GetCurrentClusterString(const hb_glyph_info_t* glyph_info, int glyph_count, int glyph_index,
|
|
|
+ Character first_character, StringView string, int& cluster_codepoint_count) const
|
|
|
+{
|
|
|
+ unsigned int cluster_index = glyph_info[glyph_index].cluster;
|
|
|
+ cluster_codepoint_count = 1;
|
|
|
+ int cluster_offset = glyph_index + 1;
|
|
|
+ int cluster_string_size = (int)Rml::StringUtilities::BytesUTF8(first_character);
|
|
|
+
|
|
|
+ // Continue counting characters that are part of the same cluster.
|
|
|
+ while (cluster_offset < (int)glyph_count && glyph_info[cluster_offset].cluster == cluster_index)
|
|
|
+ {
|
|
|
+ Character current_cluster_character =
|
|
|
+ Rml::StringUtilities::ToCharacter(string.begin() + glyph_info[glyph_index].cluster + cluster_string_size, string.end());
|
|
|
+ cluster_string_size += (int)Rml::StringUtilities::BytesUTF8(current_cluster_character);
|
|
|
+
|
|
|
+ ++cluster_codepoint_count;
|
|
|
+ ++cluster_offset;
|
|
|
+ }
|
|
|
+
|
|
|
+ return StringView(string.begin() + glyph_info[glyph_index].cluster, string.begin() + glyph_info[glyph_index].cluster + cluster_string_size);
|
|
|
+}
|