Skip to content

Commit

Permalink
Implement fallback font support to HarfBuzz sample
Browse files Browse the repository at this point in the history
  • Loading branch information
TriangulumDesire committed Jul 17, 2024
1 parent 51af29b commit fea8921
Show file tree
Hide file tree
Showing 19 changed files with 1,093 additions and 106 deletions.
8 changes: 8 additions & 0 deletions Samples/basic/harfbuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ add_executable(${TARGET_NAME} WIN32
src/FreeTypeInterface.cpp
src/FreeTypeInterface.h
src/LanguageData.h
src/TextureLayout.cpp
src/TextureLayout.h
src/TextureLayoutRectangle.cpp
src/TextureLayoutRectangle.h
src/TextureLayoutRow.cpp
src/TextureLayoutRow.h
src/TextureLayoutTexture.cpp
src/TextureLayoutTexture.h
src/main.cpp
)

Expand Down
8 changes: 4 additions & 4 deletions Samples/basic/harfbuzz/src/FontEngineInterfaceHarfBuzz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ void FontEngineInterfaceHarfBuzz::Shutdown()
FontProvider::Shutdown();
}

bool FontEngineInterfaceHarfBuzz::LoadFontFace(const String& file_name, bool /*fallback_face*/, Style::FontWeight weight)
bool FontEngineInterfaceHarfBuzz::LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight)
{
return FontProvider::LoadFontFace(file_name, weight);
return FontProvider::LoadFontFace(file_name, fallback_face, weight);
}

bool FontEngineInterfaceHarfBuzz::LoadFontFace(Span<const byte> data, const String& font_family, Style::FontStyle style, Style::FontWeight weight,
bool /*fallback_face*/)
bool fallback_face)
{
return FontProvider::LoadFontFace(data, font_family, style, weight);
return FontProvider::LoadFontFace(data, font_family, style, weight, fallback_face);
}

FontFaceHandle FontEngineInterfaceHarfBuzz::GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size)
Expand Down
100 changes: 87 additions & 13 deletions Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ const FontGlyphMap& FontFaceHandleHarfBuzz::GetGlyphs() const
return glyphs;
}

const FallbackFontGlyphMap& FontFaceHandleHarfBuzz::GetFallbackGlyphs() const
{
return fallback_glyphs;
}

int FontFaceHandleHarfBuzz::GetStringWidth(StringView string, const TextShapingContext& text_shaping_context,
const LanguageDataMap& registered_languages, Character prior_character)
{
Expand All @@ -117,7 +122,7 @@ int FontFaceHandleHarfBuzz::GetStringWidth(StringView string, const TextShapingC
continue;

FontGlyphIndex glyph_codepoint = glyph_info[g].codepoint;
const FontGlyph* glyph = GetOrAppendGlyph(glyph_codepoint);
const FontGlyph* glyph = GetOrAppendGlyph(glyph_codepoint, character);
if (!glyph)
continue;

Expand Down Expand Up @@ -216,7 +221,7 @@ bool FontFaceHandleHarfBuzz::GenerateLayerTexture(Vector<byte>& texture_data, Ve
return false;
}

return it->layer->GenerateTexture(texture_data, texture_dimensions, texture_id, glyphs);
return it->layer->GenerateTexture(texture_data, texture_dimensions, texture_id, glyphs, fallback_glyphs);
}

int FontFaceHandleHarfBuzz::GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, StringView string, const Vector2f position,
Expand Down Expand Up @@ -280,13 +285,14 @@ int FontFaceHandleHarfBuzz::GenerateString(RenderManager& render_manager, Textur

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());

// Don't render control characters.
if (IsASCIIControlCharacter(character))
continue;

FontGlyphIndex glyph_codepoint = glyph_info[g].codepoint;
const FontGlyph* glyph = GetOrAppendGlyph(glyph_codepoint);
const FontGlyph* glyph = GetOrAppendGlyph(glyph_codepoint, character);
if (!glyph)
continue;

Expand All @@ -298,7 +304,8 @@ int FontFaceHandleHarfBuzz::GenerateString(RenderManager& render_manager, Textur
if (layer == base_layer && glyph->color_format == ColorFormat::RGBA8)
glyph_color = ColourbPremultiplied(layer_colour.alpha, layer_colour.alpha);

layer->GenerateGeometry(&mesh_list[geometry_index], glyph_codepoint, Vector2f(position.x + line_width, position.y), glyph_color);
layer->GenerateGeometry(&mesh_list[geometry_index], glyph_codepoint, character, Vector2f(position.x + line_width, position.y),
glyph_color);

line_width += glyph->advance;
line_width += (int)text_shaping_context.letter_spacing;
Expand Down Expand Up @@ -342,12 +349,40 @@ int FontFaceHandleHarfBuzz::GetVersion() const
return version;
}

bool FontFaceHandleHarfBuzz::AppendGlyph(FontGlyphIndex glyph_index)
bool FontFaceHandleHarfBuzz::AppendGlyph(FontGlyphIndex glyph_index, Character character)
{
bool result = FreeType::AppendGlyph(ft_face, metrics.size, glyph_index, glyphs);
bool result = FreeType::AppendGlyph(ft_face, metrics.size, glyph_index, character, glyphs);
return result;
}

bool FontFaceHandleHarfBuzz::AppendFallbackGlyph(Character character)
{
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;

const FontGlyphIndex character_index = FreeType::GetGlyphIndexFromCharacter(fallback_face->ft_face, character);
if (character_index == 0)
continue;

const FontGlyph* glyph = fallback_face->GetOrAppendGlyph(character_index, character, false);
if (glyph)
{
// Insert the new glyph into our own set of fallback glyphs
auto pair = fallback_glyphs.emplace(character, glyph->WeakCopy());
if (pair.second)
is_layers_dirty = true;

return true;
}
}

return false;
}

void FontFaceHandleHarfBuzz::FillKerningPairCache()
{
if (!has_kerning)
Expand Down Expand Up @@ -391,14 +426,21 @@ int FontFaceHandleHarfBuzz::GetKerning(FontGlyphIndex lhs, FontGlyphIndex rhs) c
return result;
}

const FontGlyph* FontFaceHandleHarfBuzz::GetOrAppendGlyph(FontGlyphIndex& glyph_index)
const FontGlyph* FontFaceHandleHarfBuzz::GetOrAppendGlyph(FontGlyphIndex glyph_index, Character& character, bool look_in_fallback_fonts)
{
if (glyph_index == 0 && look_in_fallback_fonts && character != Character::Replacement)
{
auto fallback_glyph = GetOrAppendFallbackGlyph(character);
if (fallback_glyph != nullptr)
return fallback_glyph;
}

auto glyph_location = glyphs.find(glyph_index);
if (glyph_location == glyphs.cend())
{
bool result = false;
if (glyph_index != 0)
result = AppendGlyph(glyph_index);
result = AppendGlyph(glyph_index, character);

if (result)
{
Expand All @@ -415,10 +457,39 @@ const FontGlyph* FontFaceHandleHarfBuzz::GetOrAppendGlyph(FontGlyphIndex& glyph_
return nullptr;
}

const FontGlyph* glyph = &glyph_location->second;
if (glyph_index == 0)
character = Character::Replacement;

const FontGlyph* glyph = &glyph_location->second.bitmap;
return glyph;
}

const FontGlyph* FontFaceHandleHarfBuzz::GetOrAppendFallbackGlyph(Character character)
{
auto fallback_glyph_location = fallback_glyphs.find(character);
if (fallback_glyph_location != fallback_glyphs.cend())
return &fallback_glyph_location->second;

bool result = AppendFallbackGlyph(character);

if (result)
{
fallback_glyph_location = fallback_glyphs.find(character);
if (fallback_glyph_location == fallback_glyphs.cend())
{
RMLUI_ERROR;
return nullptr;
}

is_layers_dirty = true;
}
else
return nullptr;

const FontGlyph* fallback_glyph = &fallback_glyph_location->second;
return fallback_glyph;
}

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.
Expand Down Expand Up @@ -519,9 +590,12 @@ void FontFaceHandleHarfBuzz::ConfigureTextShapingBuffer(hb_buffer_t* shaping_buf
else
{
// Language not registered; determine best text-flow direction based on script.
bool is_common_right_to_left_script =
script == HB_SCRIPT_ARABIC || script == HB_SCRIPT_HEBREW || script == HB_SCRIPT_SYRIAC || script == HB_SCRIPT_THAANA;
hb_buffer_set_direction(shaping_buffer, is_common_right_to_left_script ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
hb_direction_t 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;

Expand Down
20 changes: 16 additions & 4 deletions Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class FontFaceHandleHarfBuzz : public Rml::NonCopyMoveable {
const FontMetrics& GetFontMetrics() const;

const FontGlyphMap& GetGlyphs() const;
const FallbackFontGlyphMap& GetFallbackGlyphs() const;

/// Returns the width a string will take up if rendered with this handle.
/// @param[in] string The string to measure.
Expand Down Expand Up @@ -112,8 +113,11 @@ class FontFaceHandleHarfBuzz : public Rml::NonCopyMoveable {
int GetVersion() const;

private:
// Build and append glyph to 'glyphs'
bool AppendGlyph(FontGlyphIndex glyph_index);
// Build and append glyph to 'glyphs'.
bool AppendGlyph(FontGlyphIndex glyph_index, Character character);

// Build and append fallback glyph to 'fallback_glyphs'.
bool AppendFallbackGlyph(Character character);

// Build a kerning cache for common characters.
void FillKerningPairCache();
Expand All @@ -122,9 +126,16 @@ class FontFaceHandleHarfBuzz : public Rml::NonCopyMoveable {
int GetKerning(FontGlyphIndex lhs, FontGlyphIndex rhs) const;

/// Retrieve a glyph from the given code index, building and appending a new glyph if not already built.
/// @param[in-out] glyph_index The glyph index, can be changed e.g. to the replacement character if no glyph is found.
/// @param[in] glyph_index The glyph index.
/// @param[in-out] character The character codepoint, can be changed e.g. to the replacement character if no glyph is found..
/// @param[in] look_in_fallback_fonts Look for the glyph in fallback fonts if not found locally, adding it to our fallback glyph map.
/// @return The font glyph for the returned glyph index.
const FontGlyph* GetOrAppendGlyph(FontGlyphIndex& glyph_index);
const FontGlyph* GetOrAppendGlyph(FontGlyphIndex glyph_index, Character& character, bool look_in_fallback_fonts = true);

/// Retrieve a fallback glyph from the given character, building and appending a new fallback glyph if not already built.
/// @param[in] character The character codepoint.
/// @return The fallback font glyph for character.
const FontGlyph* GetOrAppendFallbackGlyph(Character character);

// Regenerate layers if dirty, such as after adding new glyphs.
bool UpdateLayersOnDirty();
Expand All @@ -140,6 +151,7 @@ class FontFaceHandleHarfBuzz : public Rml::NonCopyMoveable {
const LanguageDataMap& registered_languages);

FontGlyphMap glyphs;
FallbackFontGlyphMap fallback_glyphs;

struct EffectLayerPair {
const FontEffect* font_effect;
Expand Down
Loading

0 comments on commit fea8921

Please sign in to comment.