Browse Source

Add a kerning pair cache for fonts

Michael Ragazzon 5 years ago
parent
commit
f5a7f9b0f2

+ 2 - 2
Samples/assets/invader.rcss

@@ -349,8 +349,8 @@ dataselect selectvalue
 	width: auto;
 	width: auto;
 	margin-right: 30px;
 	margin-right: 30px;
 	
 	
-	height: 27px;
-	padding: 10px 10px 0px 10px;
+	height: 26px;
+	padding: 11px 10px 0px 10px;
 
 
 	decorator: image( selectvalue  );
 	decorator: image( selectvalue  );
 }
 }

+ 54 - 5
Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp

@@ -36,6 +36,9 @@
 
 
 namespace Rml {
 namespace Rml {
 
 
+static constexpr char32_t KerningCache_AsciiSubsetBegin = 32;
+static constexpr char32_t KerningCache_AsciiSubsetLast = 126;
+
 FontFaceHandleDefault::FontFaceHandleDefault()
 FontFaceHandleDefault::FontFaceHandleDefault()
 {
 {
 	base_layer = nullptr;
 	base_layer = nullptr;
@@ -60,6 +63,9 @@ bool FontFaceHandleDefault::Initialize(FontFaceHandleFreetype face, int font_siz
 		return false;
 		return false;
 	}
 	}
 
 
+	has_kerning = FreeType::HasKerning(ft_face);
+	FillKerningPairCache();
+
 	// Generate the default layer and layer configuration.
 	// Generate the default layer and layer configuration.
 	base_layer = GetOrCreateLayer(nullptr);
 	base_layer = GetOrCreateLayer(nullptr);
 	layer_configurations.push_back(LayerConfiguration{ base_layer });
 	layer_configurations.push_back(LayerConfiguration{ base_layer });
@@ -116,8 +122,8 @@ int FontFaceHandleDefault::GetStringWidth(const String& string, Character prior_
 			continue;
 			continue;
 
 
 		// Adjust the cursor for the kerning between this character and the previous one.
 		// Adjust the cursor for the kerning between this character and the previous one.
-		if (prior_character != Character::Null)
-			width += GetKerning(prior_character, character);
+		width += GetKerning(prior_character, character);
+
 		// Adjust the cursor for this character's advance.
 		// Adjust the cursor for this character's advance.
 		width += glyph->advance;
 		width += glyph->advance;
 
 
@@ -268,8 +274,7 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String&
 				continue;
 				continue;
 
 
 			// Adjust the cursor for the kerning between this character and the previous one.
 			// Adjust the cursor for the kerning between this character and the previous one.
-			if (prior_character != Character::Null)
-				line_width += GetKerning(prior_character, character);
+			line_width += GetKerning(prior_character, character);
 
 
 			layer->GenerateGeometry(&geometry[geometry_index], character, Vector2f(position.x + line_width, position.y), layer_colour);
 			layer->GenerateGeometry(&geometry[geometry_index], character, Vector2f(position.x + line_width, position.y), layer_colour);
 
 
@@ -321,9 +326,53 @@ bool FontFaceHandleDefault::AppendGlyph(Character character)
 	return result;
 	return result;
 }
 }
 
 
+void FontFaceHandleDefault::FillKerningPairCache()
+{
+	if (!has_kerning)
+		return;
+
+	for (char32_t i = KerningCache_AsciiSubsetBegin; i <= KerningCache_AsciiSubsetLast; i++)
+	{
+		for (char32_t j = KerningCache_AsciiSubsetBegin; j <= KerningCache_AsciiSubsetLast; j++)
+		{
+			const bool first_iteration = (i == KerningCache_AsciiSubsetBegin && j == KerningCache_AsciiSubsetBegin);
+
+			// Fetch the kerning from the font face. Submit zero font size on subsequent iterations for performance reasons.
+			const int kerning = FreeType::GetKerning(ft_face, first_iteration ? metrics.size : 0, Character(i), Character(j));
+			if (kerning != 0)
+			{
+				kerning_pair_cache.emplace(AsciiPair((i << 8) | j), KerningIntType(kerning));
+			}
+		}
+	}
+}
+
 int FontFaceHandleDefault::GetKerning(Character lhs, Character rhs) const
 int FontFaceHandleDefault::GetKerning(Character lhs, Character rhs) const
 {
 {
-	int result = FreeType::GetKerning(ft_face, metrics.size, lhs, rhs);
+	static_assert(' ' == 32, "Only ASCII/UTF8 character set supported.");
+
+	// Check if we have no kerning, or if we are have a control character.
+	if (!has_kerning || char32_t(lhs) < ' ' || char32_t(rhs) < ' ')
+		return 0;
+
+	// See if the kerning pair has been cached.
+	const bool lhs_in_cache = (char32_t(lhs) >= KerningCache_AsciiSubsetBegin && char32_t(lhs) <= KerningCache_AsciiSubsetLast);
+	const bool rhs_in_cache = (char32_t(rhs) >= KerningCache_AsciiSubsetBegin && char32_t(rhs) <= KerningCache_AsciiSubsetLast);
+
+	if (lhs_in_cache && rhs_in_cache)
+	{
+		const auto it = kerning_pair_cache.find(AsciiPair((int(lhs) << 8) | int(rhs)));
+
+		if (it != kerning_pair_cache.end())
+		{
+			return it->second;
+		}
+
+		return 0;
+	}
+
+	// Fetch it from the font face instead.
+	const int result = FreeType::GetKerning(ft_face, metrics.size, lhs, rhs);
 	return result;
 	return result;
 }
 }
 
 

+ 12 - 2
Source/Core/FontEngineDefault/FontFaceHandleDefault.h

@@ -98,11 +98,14 @@ public:
 	/// Version is changed whenever the layers are dirtied, requiring regeneration of string geometry.
 	/// Version is changed whenever the layers are dirtied, requiring regeneration of string geometry.
 	int GetVersion() const;
 	int GetVersion() const;
 
 
-
 private:
 private:
 	// Build and append glyph to 'glyphs'
 	// Build and append glyph to 'glyphs'
 	bool AppendGlyph(Character character);
 	bool AppendGlyph(Character character);
 
 
+	// Build a kerning cache for common characters.
+	void FillKerningPairCache();
+
+	// Return the kerning for a character pair.
 	int GetKerning(Character lhs, Character rhs) const;
 	int GetKerning(Character lhs, Character rhs) const;
 
 
 	/// Retrieve a glyph from the given code point, building and appending a new glyph if not already built.
 	/// Retrieve a glyph from the given code point, building and appending a new glyph if not already built.
@@ -137,8 +140,15 @@ private:
 	// Each font layer that generated geometry or textures, indexed by the font-effect's fingerprint key.
 	// Each font layer that generated geometry or textures, indexed by the font-effect's fingerprint key.
 	FontLayerCache layer_cache;
 	FontLayerCache layer_cache;
 
 
-	int version = 0;
+	// Pre-cache kerning pairs for some ascii subset of all characters.
+	using AsciiPair = std::uint16_t;
+	using KerningIntType = std::int16_t;
+	using KerningPairs = UnorderedMap< AsciiPair, KerningIntType >;
+	KerningPairs kerning_pair_cache;
+
+	bool has_kerning = false;
 	bool is_layers_dirty = false;
 	bool is_layers_dirty = false;
+	int version = 0;
 
 
 	// All configurations currently in use on this handle. New configurations will be generated as required.
 	// All configurations currently in use on this handle. New configurations will be generated as required.
 	LayerConfigurationList layer_configurations;
 	LayerConfigurationList layer_configurations;

+ 16 - 6
Source/Core/FontEngineDefault/FreeTypeInterface.cpp

@@ -169,17 +169,20 @@ int FreeType::GetKerning(FontFaceHandleFreetype face, int font_size, Character l
 {
 {
 	FT_Face ft_face = (FT_Face)face;
 	FT_Face ft_face = (FT_Face)face;
 
 
-	if (!FT_HAS_KERNING(ft_face))
-		return 0;
+	RMLUI_ASSERT(FT_HAS_KERNING(ft_face));
 
 
 	// Set face size again in case it was used at another size in another font face handle.
 	// Set face size again in case it was used at another size in another font face handle.
-	FT_Error ft_error = FT_Set_Char_Size(ft_face, 0, font_size << 6, 0, 0);
-	if (ft_error)
-		return 0;
+	// Font size value of zero assumes it is already set.
+	if (font_size > 0)
+	{
+		FT_Error ft_error = FT_Set_Char_Size(ft_face, 0, font_size << 6, 0, 0);
+		if (ft_error)
+			return 0;
+	}
 
 
 	FT_Vector ft_kerning;
 	FT_Vector ft_kerning;
 
 
-	ft_error = FT_Get_Kerning(
+	FT_Error ft_error = FT_Get_Kerning(
 		ft_face,
 		ft_face,
 		FT_Get_Char_Index(ft_face, (FT_ULong)lhs),
 		FT_Get_Char_Index(ft_face, (FT_ULong)lhs),
 		FT_Get_Char_Index(ft_face, (FT_ULong)rhs),
 		FT_Get_Char_Index(ft_face, (FT_ULong)rhs),
@@ -194,6 +197,13 @@ int FreeType::GetKerning(FontFaceHandleFreetype face, int font_size, Character l
 	return kerning;
 	return kerning;
 }
 }
 
 
+bool FreeType::HasKerning(FontFaceHandleFreetype face)
+{
+	FT_Face ft_face = (FT_Face)face;
+
+	return FT_HAS_KERNING(ft_face);
+}
+
 
 
 
 
 static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs)
 static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs)

+ 4 - 0
Source/Core/FontEngineDefault/FreeTypeInterface.h

@@ -56,8 +56,12 @@ bool InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphM
 bool AppendGlyph(FontFaceHandleFreetype face, int font_size, Character character, FontGlyphMap& glyphs);
 bool AppendGlyph(FontFaceHandleFreetype face, int font_size, Character character, FontGlyphMap& glyphs);
 
 
 // Returns the kerning between two characters.
 // Returns the kerning between two characters.
+// 'font_size' value of zero assumes the font size is already set on the face, and skips this step for performance reasons.
 int GetKerning(FontFaceHandleFreetype face, int font_size, Character lhs, Character rhs);
 int GetKerning(FontFaceHandleFreetype face, int font_size, Character lhs, Character rhs);
 
 
+// Returns true if the font face has kerning.
+bool HasKerning(FontFaceHandleFreetype face);
+
 }
 }
 } // namespace Rml
 } // namespace Rml
 #endif
 #endif