Browse Source

Implement fallback font support to HarfBuzz sample (#635)

Matthew Schäfer 1 year ago
parent
commit
84b9cc192e

+ 8 - 0
Samples/basic/harfbuzz/CMakeLists.txt

@@ -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
 )
 

+ 4 - 4
Samples/basic/harfbuzz/src/FontEngineInterfaceHarfBuzz.cpp

@@ -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)

+ 87 - 13
Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.cpp

@@ -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)
 {
@@ -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;
 
@@ -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,
@@ -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;
 
@@ -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;
@@ -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)
@@ -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)
 		{
@@ -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.
@@ -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;
 

+ 16 - 4
Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.h

@@ -70,6 +70,7 @@ public:
 	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.
@@ -112,8 +113,11 @@ public:
 	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();
@@ -122,9 +126,16 @@ private:
 	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();
@@ -140,6 +151,7 @@ private:
 		const LanguageDataMap& registered_languages);
 
 	FontGlyphMap glyphs;
+	FallbackFontGlyphMap fallback_glyphs;
 
 	struct EffectLayerPair {
 		const FontEffect* font_effect;

+ 123 - 56
Samples/basic/harfbuzz/src/FontFaceLayer.cpp

@@ -29,6 +29,7 @@
 #include "FontFaceLayer.h"
 #include "FontFaceHandleHarfBuzz.h"
 #include <string.h>
+#include <type_traits>
 
 FontFaceLayer::FontFaceLayer(const SharedPtr<const FontEffect>& _effect) : colour(255, 255, 255)
 {
@@ -52,6 +53,7 @@ bool FontFaceLayer::Generate(const FontFaceHandleHarfBuzz* handle, const FontFac
 	}
 
 	const FontGlyphMap& glyphs = handle->GetGlyphs();
+	const FallbackFontGlyphMap& fallback_glyphs = handle->GetFallbackGlyphs();
 
 	// Generate the new layout.
 	if (clone)
@@ -68,57 +70,40 @@ bool FontFaceLayer::Generate(const FontFaceHandleHarfBuzz* handle, const FontFac
 			for (auto& pair : glyphs)
 			{
 				FontGlyphIndex glyph_index = pair.first;
-				const FontGlyph& glyph = pair.second;
+				const FontGlyph& glyph = pair.second.bitmap;
+				const Character glyph_character = pair.second.character;
 
-				auto it = character_boxes.find(glyph_index);
-				if (it == character_boxes.end())
-				{
-					// This can happen if the layers have been dirtied in FontHandleDefault. We will
-					// probably be regenerated soon, just skip the character for now.
-					continue;
-				}
-
-				TextureBox& box = it->second;
+				CloneTextureBox(glyph, glyph_index, glyph_character);
+			}
 
-				Vector2i glyph_origin = Vector2i(box.origin);
-				Vector2i glyph_dimensions = Vector2i(box.dimensions);
+			for (auto& pair : fallback_glyphs)
+			{
+				const Character glyph_character = pair.first;
+				const FontGlyph& glyph = pair.second;
 
-				if (effect->GetGlyphMetrics(glyph_origin, glyph_dimensions, glyph))
-					box.origin = Vector2f(glyph_origin);
-				else
-					box.texture_index = -1;
+				CloneTextureBox(glyph, 0, glyph_character);
 			}
 		}
 	}
 	else
 	{
 		// Initialise the texture layout for the glyphs.
-		character_boxes.reserve(glyphs.size());
+		character_boxes.reserve(glyphs.size() + fallback_glyphs.size());
 		for (auto& pair : glyphs)
 		{
 			FontGlyphIndex glyph_index = pair.first;
-			const FontGlyph& glyph = pair.second;
-
-			Vector2i glyph_origin(0, 0);
-			Vector2i glyph_dimensions = glyph.bitmap_dimensions;
-
-			// Adjust glyph origin / dimensions for the font effect.
-			if (effect)
-			{
-				if (!effect->GetGlyphMetrics(glyph_origin, glyph_dimensions, glyph))
-					continue;
-			}
-
-			TextureBox box;
-			box.origin = Vector2f(float(glyph_origin.x + glyph.bearing.x), float(glyph_origin.y - glyph.bearing.y));
-			box.dimensions = Vector2f(glyph_dimensions);
+			const FontGlyph& glyph = pair.second.bitmap;
+			Character glyph_character = pair.second.character;
 
-			RMLUI_ASSERT(box.dimensions.x >= 0 && box.dimensions.y >= 0);
+			CreateTextureLayout(glyph, glyph_index, glyph_character);
+		}
 
-			character_boxes[glyph_index] = box;
+		for (auto& pair : fallback_glyphs)
+		{
+			Character glyph_character = pair.first;
+			const FontGlyph& glyph = pair.second;
 
-			// Add the character's dimensions into the texture layout engine.
-			texture_layout.AddRectangle((int)glyph_index, glyph_dimensions);
+			CreateTextureLayout(glyph, 0, glyph_character);
 		}
 
 		constexpr int max_texture_dimensions = 1024;
@@ -132,11 +117,11 @@ bool FontFaceLayer::Generate(const FontFaceHandleHarfBuzz* handle, const FontFac
 		// appropriate and generating geometry.
 		for (int i = 0; i < texture_layout.GetNumRectangles(); ++i)
 		{
-			Rml::TextureLayoutRectangle& rectangle = texture_layout.GetRectangle(i);
-			const Rml::TextureLayoutTexture& texture = texture_layout.GetTexture(rectangle.GetTextureIndex());
-			FontGlyphIndex glyph_index = (FontGlyphIndex)rectangle.GetId();
-			RMLUI_ASSERT(character_boxes.find(glyph_index) != character_boxes.end());
-			TextureBox& box = character_boxes[glyph_index];
+			TextureLayoutRectangle& rectangle = texture_layout.GetRectangle(i);
+			const TextureLayoutTexture& texture = texture_layout.GetTexture(rectangle.GetTextureIndex());
+			uint64_t font_glyph_id = rectangle.GetId();
+			RMLUI_ASSERT(character_boxes.find(font_glyph_id) != character_boxes.end());
+			TextureBox& box = character_boxes[font_glyph_id];
 
 			// Set the character's texture index.
 			box.texture_index = rectangle.GetTextureIndex();
@@ -177,7 +162,8 @@ bool FontFaceLayer::Generate(const FontFaceHandleHarfBuzz* handle, const FontFac
 	return true;
 }
 
-bool FontFaceLayer::GenerateTexture(Vector<byte>& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs)
+bool FontFaceLayer::GenerateTexture(Vector<byte>& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs,
+	const FallbackFontGlyphMap& fallback_glyphs)
 {
 	if (texture_id < 0 || texture_id > texture_layout.GetNumTextures())
 		return false;
@@ -188,33 +174,52 @@ bool FontFaceLayer::GenerateTexture(Vector<byte>& texture_data, Vector2i& textur
 
 	for (int i = 0; i < texture_layout.GetNumRectangles(); ++i)
 	{
-		Rml::TextureLayoutRectangle& rectangle = texture_layout.GetRectangle(i);
-		FontGlyphIndex glyph_index = (FontGlyphIndex)rectangle.GetId();
-		RMLUI_ASSERT(character_boxes.find(glyph_index) != character_boxes.end());
+		TextureLayoutRectangle& rectangle = texture_layout.GetRectangle(i);
+		uint64_t font_glyph_id = rectangle.GetId();
+		RMLUI_ASSERT(character_boxes.find(font_glyph_id) != character_boxes.end());
 
-		TextureBox& box = character_boxes[glyph_index];
+		TextureBox& box = character_boxes[font_glyph_id];
 
 		if (box.texture_index != texture_id)
 			continue;
 
-		auto it = glyphs.find((FontGlyphIndex)rectangle.GetId());
-		if (it == glyphs.end())
-			continue;
+		const FontGlyph* glyph = nullptr;
+		FontGlyphIndex glyph_index = GetFontGlyphIndexFromID(font_glyph_id);
+		Rml::Character glyph_character = GetCharacterCodepointFromID(font_glyph_id);
 
-		const FontGlyph& glyph = it->second;
+		// Get the glyph bitmap by looking it up with the glyph index.
+		auto it = glyphs.find(glyph_index);
+		if (it == glyphs.end() || glyph_index == 0)
+		{
+			// Glyph was not found; attempt to find it in the fallback glyphs.
+			auto fallback_it = fallback_glyphs.find(glyph_character);
+			if (fallback_it == fallback_glyphs.end())
+				if (it != glyphs.end())
+					// Fallback glyph was not found, but replacement glyph bitmap exists, so use it instead.
+					glyph = &it->second.bitmap;
+				else
+					// No fallback glyph nor replacement glyph bitmap was found; ignore this glyph.
+					continue;
+			else
+				// Fallback glyph was found.
+				glyph = &fallback_it->second;
+		}
+		else
+			// Glyph was found.
+			glyph = &it->second.bitmap;
 
 		if (effect == nullptr)
 		{
 			// Copy the glyph's bitmap data into its allocated texture.
-			if (glyph.bitmap_data)
+			if (glyph->bitmap_data)
 			{
 				byte* destination = rectangle.GetTextureData();
-				const byte* source = glyph.bitmap_data;
-				const int num_bytes_per_line = glyph.bitmap_dimensions.x * (glyph.color_format == ColorFormat::RGBA8 ? 4 : 1);
+				const byte* source = glyph->bitmap_data;
+				const int num_bytes_per_line = glyph->bitmap_dimensions.x * (glyph->color_format == ColorFormat::RGBA8 ? 4 : 1);
 
-				for (int j = 0; j < glyph.bitmap_dimensions.y; ++j)
+				for (int j = 0; j < glyph->bitmap_dimensions.y; ++j)
 				{
-					switch (glyph.color_format)
+					switch (glyph->color_format)
 					{
 					case ColorFormat::A8:
 					{
@@ -238,7 +243,7 @@ bool FontFaceLayer::GenerateTexture(Vector<byte>& texture_data, Vector2i& textur
 		}
 		else
 		{
-			effect->GenerateGlyphTexture(rectangle.GetTextureData(), Vector2i(box.dimensions), rectangle.GetTextureStride(), glyph);
+			effect->GenerateGlyphTexture(rectangle.GetTextureData(), Vector2i(box.dimensions), rectangle.GetTextureStride(), *glyph);
 		}
 	}
 
@@ -267,3 +272,65 @@ ColourbPremultiplied FontFaceLayer::GetColour(float opacity) const
 {
 	return colour.ToPremultiplied(opacity);
 }
+
+uint64_t FontFaceLayer::CreateFontGlyphID(const FontGlyphIndex glyph_index, const Character character_code) const
+{
+	return (static_cast<uint64_t>(glyph_index) << (sizeof(Character) * 8)) | static_cast<uint64_t>(std::underlying_type_t<Character>(character_code));
+}
+
+FontGlyphIndex FontFaceLayer::GetFontGlyphIndexFromID(const uint64_t glyph_id) const
+{
+	return static_cast<FontGlyphIndex>(glyph_id >> (sizeof(Character) * 8));
+}
+
+Character FontFaceLayer::GetCharacterCodepointFromID(const uint64_t glyph_id) const
+{
+	return static_cast<Character>(glyph_id & static_cast<std::underlying_type_t<Character>>(-1));
+}
+
+void FontFaceLayer::CreateTextureLayout(const FontGlyph& glyph, FontGlyphIndex glyph_index, Character glyph_character)
+{
+	Vector2i glyph_origin(0, 0);
+	Vector2i glyph_dimensions = glyph.bitmap_dimensions;
+
+	// Adjust glyph origin / dimensions for the font effect.
+	if (effect)
+	{
+		if (!effect->GetGlyphMetrics(glyph_origin, glyph_dimensions, glyph))
+			return;
+	}
+
+	TextureBox box;
+	box.origin = Vector2f(float(glyph_origin.x + glyph.bearing.x), float(glyph_origin.y - glyph.bearing.y));
+	box.dimensions = Vector2f(glyph_dimensions);
+
+	RMLUI_ASSERT(box.dimensions.x >= 0 && box.dimensions.y >= 0);
+
+	uint64_t font_glyph_id = CreateFontGlyphID(glyph_index, glyph_character);
+	character_boxes[font_glyph_id] = box;
+
+	// Add the character's dimensions into the texture layout engine.
+	texture_layout.AddRectangle(font_glyph_id, glyph_dimensions);
+}
+
+void FontFaceLayer::CloneTextureBox(const FontGlyph& glyph, FontGlyphIndex glyph_index, Character glyph_character)
+{
+	auto it = character_boxes.find(CreateFontGlyphID(glyph_index, glyph_character));
+	if (it == character_boxes.end())
+	{
+		// This can happen if the layers have been dirtied in FontHandleDefault. We will
+		// probably be regenerated soon, just skip the character for now.
+		return;
+	}
+
+	TextureBox& box = it->second;
+
+	Vector2i glyph_origin = Vector2i(box.origin);
+	Vector2i glyph_dimensions = Vector2i(box.dimensions);
+
+	if (effect->GetGlyphMetrics(glyph_origin, glyph_dimensions, glyph))
+		box.origin = Vector2f(glyph_origin);
+	else
+		box.texture_index = -1;
+
+}

+ 21 - 6
Samples/basic/harfbuzz/src/FontFaceLayer.h

@@ -31,6 +31,7 @@
 
 #include "FontGlyph.h"
 #include "TextureLayout.h"
+#include "TextureLayoutRectangle.h"
 #include <RmlUi/Core.h>
 
 using Rml::byte;
@@ -42,14 +43,13 @@ using Rml::ColorFormat;
 using Rml::Colourb;
 using Rml::ColourbPremultiplied;
 using Rml::FontEffect;
+using Rml::FontGlyph;
 using Rml::Geometry;
 using Rml::Mesh;
 using Rml::RenderManager;
 using Rml::SharedPtr;
 using Rml::Texture;
 using Rml::TexturedMesh;
-using Rml::TextureLayout;
-using Rml::TextureLayoutRectangle;
 using Rml::UniquePtr;
 using Rml::UnorderedMap;
 using Rml::Vector;
@@ -83,17 +83,17 @@ public:
 	/// @param[out] texture_dimensions The dimensions of the texture.
 	/// @param[in] texture_id The index of the texture within the layer to generate.
 	/// @param[in] glyphs The glyphs required by the font face handle.
-	bool GenerateTexture(Vector<byte>& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs);
+	bool GenerateTexture(Vector<byte>& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs, const FallbackFontGlyphMap& fallback_glyphs);
 
 	/// Generates the geometry required to render a single character.
 	/// @param[out] mesh_list An array of meshes this layer will write to. It must be at least as big as the number of textures in this layer.
 	/// @param[in] character_code The character to generate geometry for.
 	/// @param[in] position The position of the baseline.
 	/// @param[in] colour The colour of the string.
-	inline void GenerateGeometry(TexturedMesh* mesh_list, const FontGlyphIndex glyph_index, const Vector2f position,
+	inline void GenerateGeometry(TexturedMesh* mesh_list, const FontGlyphIndex glyph_index, const Character character_code, const Vector2f position,
 		const ColourbPremultiplied colour) const
 	{
-		auto it = character_boxes.find(glyph_index);
+		auto it = character_boxes.find(CreateFontGlyphID(glyph_index, character_code));
 		if (it == character_boxes.end())
 			return;
 
@@ -119,6 +119,21 @@ public:
 	ColourbPremultiplied GetColour(float opacity) const;
 
 private:
+	/// Creates an ID for a font glyph from a glyph index and character codepoint.
+	uint64_t CreateFontGlyphID(const FontGlyphIndex glyph_index, const Character character_code) const;
+
+	/// Retrieves the font glyph index from a font glyph ID.
+	FontGlyphIndex GetFontGlyphIndexFromID(const uint64_t glyph_id) const;
+
+	/// Retrieves the character from a font glyph ID.
+	Character GetCharacterCodepointFromID(const uint64_t glyph_id) const;
+
+	/// Creates a texture layout from the given glyph bitmap and data.
+	void CreateTextureLayout(const FontGlyph& glyph, FontGlyphIndex glyph_index, Character glyph_character);
+
+	/// Clones the given glyph bitmap and data into a texture box.
+	void CloneTextureBox(const FontGlyph& glyph, FontGlyphIndex glyph_index, Character glyph_character);
+
 	struct TextureBox {
 		// The offset, in pixels, of the baseline from the start of this character's geometry.
 		Vector2f origin;
@@ -131,7 +146,7 @@ private:
 		int texture_index = -1;
 	};
 
-	using CharacterMap = UnorderedMap<FontGlyphIndex, TextureBox>;
+	using CharacterMap = UnorderedMap<uint64_t, TextureBox>;
 	using TextureList = Vector<CallbackTextureSource>;
 
 	SharedPtr<const FontEffect> effect;

+ 8 - 1
Samples/basic/harfbuzz/src/FontGlyph.h

@@ -31,7 +31,14 @@
 
 #include <RmlUi/Core.h>
 
+struct FontGlyphData
+{
+	Rml::FontGlyph bitmap;
+	Rml::Character character;
+};
+
 using FontGlyphIndex = uint32_t;
-using FontGlyphMap = Rml::UnorderedMap<FontGlyphIndex, Rml::FontGlyph>;
+using FontGlyphMap = Rml::UnorderedMap<FontGlyphIndex, FontGlyphData>;
+using FallbackFontGlyphMap = Rml::UnorderedMap<Rml::Character, Rml::FontGlyph>;
 
 #endif

+ 33 - 7
Samples/basic/harfbuzz/src/FontProvider.cpp

@@ -82,6 +82,21 @@ FontFaceHandleHarfBuzz* FontProvider::GetFontFaceHandle(const String& family, St
 	return it->second->GetFaceHandle(style, weight, size);
 }
 
+int FontProvider::CountFallbackFontFaces()
+{
+	return (int)Get().fallback_font_faces.size();
+}
+
+FontFaceHandleHarfBuzz* FontProvider::GetFallbackFontFace(int index, int font_size)
+{
+	auto& faces = FontProvider::Get().fallback_font_faces;
+
+	if (index >= 0 && index < (int)faces.size())
+		return faces[index]->GetHandle(font_size, false);
+
+	return nullptr;
+}
+
 void FontProvider::ReleaseFontResources()
 {
 	RMLUI_ASSERT(g_font_provider);
@@ -89,7 +104,7 @@ void FontProvider::ReleaseFontResources()
 		name_family.second->ReleaseFontResources();
 }
 
-bool FontProvider::LoadFontFace(const String& file_name, Style::FontWeight weight)
+bool FontProvider::LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight)
 {
 	Rml::FileInterface* file_interface = Rml::GetFileInterface();
 	Rml::FileHandle handle = file_interface->Open(file_name);
@@ -107,21 +122,22 @@ bool FontProvider::LoadFontFace(const String& file_name, Style::FontWeight weigh
 	file_interface->Read(buffer, length, handle);
 	file_interface->Close(handle);
 
-	bool result = Get().LoadFontFace({buffer, length}, std::move(buffer_ptr), file_name, {}, Style::FontStyle::Normal, weight);
+	bool result = Get().LoadFontFace({buffer, length}, fallback_face, std::move(buffer_ptr), file_name, {}, Style::FontStyle::Normal, weight);
 
 	return result;
 }
 
-bool FontProvider::LoadFontFace(Span<const byte> data, const String& font_family, Style::FontStyle style, Style::FontWeight weight)
+bool FontProvider::LoadFontFace(Span<const byte> data, const String& font_family, Style::FontStyle style, Style::FontWeight weight,
+	bool fallback_face)
 {
 	const String source = "memory";
 
-	bool result = Get().LoadFontFace(data, nullptr, source, font_family, style, weight);
+	bool result = Get().LoadFontFace(data, fallback_face, nullptr, source, font_family, style, weight);
 
 	return result;
 }
 
-bool FontProvider::LoadFontFace(Span<const byte> data, UniquePtr<byte[]> face_memory, const String& source, String font_family,
+bool FontProvider::LoadFontFace(Span<const byte> data, bool fallback_face, UniquePtr<byte[]> face_memory, const String& source, String font_family,
 	Style::FontStyle style, Style::FontWeight weight)
 {
 	using Style::FontWeight;
@@ -196,7 +212,7 @@ bool FontProvider::LoadFontFace(Span<const byte> data, UniquePtr<byte[]> face_me
 		const FontWeight variation_weight = (variation.weight == FontWeight::Auto ? weight : variation.weight);
 		const String font_face_description = Rml::GetFontFaceDescription(font_family, style, variation_weight);
 
-		if (!AddFace(ft_face, font_family, style, variation_weight, std::move(face_memory)))
+		if (!AddFace(ft_face, font_family, style, variation_weight, fallback_face, std::move(face_memory)))
 		{
 			Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to load font face %s from '%s'.", font_face_description.c_str(), source.c_str());
 			return false;
@@ -209,7 +225,7 @@ bool FontProvider::LoadFontFace(Span<const byte> data, UniquePtr<byte[]> face_me
 }
 
 bool FontProvider::AddFace(FontFaceHandleFreetype face, const String& family, Style::FontStyle style, Style::FontWeight weight,
-	UniquePtr<byte[]> face_memory)
+	bool fallback_face, UniquePtr<byte[]> face_memory)
 {
 	if (family.empty() || weight == Style::FontWeight::Auto)
 		return false;
@@ -229,5 +245,15 @@ bool FontProvider::AddFace(FontFaceHandleFreetype face, const String& family, St
 	}
 
 	FontFace* font_face_result = font_family->AddFace(face, style, weight, std::move(face_memory));
+
+	if (font_face_result && fallback_face)
+	{
+		auto it_fallback_face = std::find(fallback_font_faces.begin(), fallback_font_faces.end(), font_face_result);
+		if (it_fallback_face == fallback_font_faces.end())
+		{
+			fallback_font_faces.push_back(font_face_result);
+		}
+	}
+
 	return static_cast<bool>(font_face_result);
 }

+ 13 - 4
Samples/basic/harfbuzz/src/FontProvider.h

@@ -67,10 +67,16 @@ public:
 	static FontFaceHandleHarfBuzz* GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size);
 
 	/// Adds a new font face to the database. The face's family, style and weight will be determined from the face itself.
-	static bool LoadFontFace(const String& file_name, Style::FontWeight weight = Style::FontWeight::Auto);
+	static bool LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight = Style::FontWeight::Auto);
 
 	/// Adds a new font face from memory.
-	static bool LoadFontFace(Span<const byte> data, const String& font_family, Style::FontStyle style, Style::FontWeight weight);
+	static bool LoadFontFace(Span<const byte> data, const String& font_family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face);
+
+	/// Return the number of fallback font faces.
+	static int CountFallbackFontFaces();
+
+	/// Return a font face handle with the given index, at the given font size.
+	static FontFaceHandleHarfBuzz* GetFallbackFontFace(int index, int font_size);
 
 	/// Releases resources owned by sized font faces, including their textures and rendered glyphs.
 	static void ReleaseFontResources();
@@ -81,15 +87,18 @@ private:
 
 	static FontProvider& Get();
 
-	bool LoadFontFace(Span<const byte> data, UniquePtr<byte[]> face_memory, const String& source, String font_family, Style::FontStyle style,
+	bool LoadFontFace(Span<const byte> data, bool fallback_face, UniquePtr<byte[]> face_memory, const String& source, String font_family,
+		Style::FontStyle style,
 		Style::FontWeight weight);
 
-	bool AddFace(FontFaceHandleFreetype face, const String& family, Style::FontStyle style, Style::FontWeight weight, UniquePtr<byte[]> face_memory);
+	bool AddFace(FontFaceHandleFreetype face, const String& family, Style::FontStyle style, Style::FontWeight weight,
+		bool fallback_face, UniquePtr<byte[]> face_memory);
 
 	using FontFaceList = Vector<FontFace*>;
 	using FontFamilyMap = UnorderedMap<String, UniquePtr<FontFamily>>;
 
 	FontFamilyMap font_families;
+	FontFaceList fallback_font_faces;
 
 	static const String debugger_font_family_name;
 };

+ 10 - 10
Samples/basic/harfbuzz/src/FreeTypeInterface.cpp

@@ -36,13 +36,12 @@
 
 namespace FreeType
 {
-static bool BuildGlyph(FT_Face ft_face, const FontGlyphIndex glyph_index, FontGlyphMap& glyphs, const float bitmap_scaling_factor);
+static bool BuildGlyph(FT_Face ft_face, const FontGlyphIndex glyph_index, Character character, FontGlyphMap& glyphs, const float bitmap_scaling_factor);
 static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs, const float bitmap_scaling_factor, const bool load_default_glyphs);
 static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics, float bitmap_scaling_factor);
 static bool SetFontSize(FT_Face ft_face, int font_size, float& out_bitmap_scaling_factor);
 static void BitmapDownscale(Rml::byte* bitmap_new, const int new_width, const int new_height, const Rml::byte* bitmap_source, const int width,
-	const int height,
-	const int pitch, const Rml::ColorFormat color_format);
+	const int height, const int pitch, const Rml::ColorFormat color_format);
 
 bool InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics, bool load_default_glyphs)
 {
@@ -63,7 +62,7 @@ bool InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphM
 	return true;
 }
 
-bool AppendGlyph(FontFaceHandleFreetype face, int font_size, FontGlyphIndex glyph_index, FontGlyphMap& glyphs)
+bool AppendGlyph(FontFaceHandleFreetype face, int font_size, FontGlyphIndex glyph_index, Character character, FontGlyphMap& glyphs)
 {
 	FT_Face ft_face = (FT_Face)face;
 
@@ -75,7 +74,7 @@ bool AppendGlyph(FontFaceHandleFreetype face, int font_size, FontGlyphIndex glyp
 	if (!SetFontSize(ft_face, font_size, bitmap_scaling_factor))
 		return false;
 
-	if (!BuildGlyph(ft_face, glyph_index, glyphs, bitmap_scaling_factor))
+	if (!BuildGlyph(ft_face, glyph_index, character, glyphs, bitmap_scaling_factor))
 		return false;
 
 	return true;
@@ -112,7 +111,7 @@ FontGlyphIndex GetGlyphIndexFromCharacter(FontFaceHandleFreetype face, Character
 	return FT_Get_Char_Index((FT_Face)face, (FT_ULong)character);
 }
 
-static bool BuildGlyph(FT_Face ft_face, const FontGlyphIndex glyph_index, FontGlyphMap& glyphs,
+static bool BuildGlyph(FT_Face ft_face, const FontGlyphIndex glyph_index, Character character, FontGlyphMap& glyphs,
 	const float bitmap_scaling_factor)
 {
 	if (glyph_index == 0)
@@ -136,7 +135,7 @@ static bool BuildGlyph(FT_Face ft_face, const FontGlyphIndex glyph_index, FontGl
 		return false;
 	}
 
-	auto result = glyphs.emplace(glyph_index, Rml::FontGlyph{});
+	auto result = glyphs.emplace(glyph_index, FontGlyphData{Rml::FontGlyph{}, character});
 	if (!result.second)
 	{
 		Rml::Log::Message(Rml::Log::LT_WARNING, "Glyph index '%u' is already loaded in the font face '%s %s'.", (unsigned int)glyph_index,
@@ -144,7 +143,7 @@ static bool BuildGlyph(FT_Face ft_face, const FontGlyphIndex glyph_index, FontGl
 		return false;
 	}
 
-	Rml::FontGlyph& glyph = result.first->second;
+	Rml::FontGlyph& glyph = result.first->second.bitmap;
 
 	FT_GlyphSlot ft_glyph = ft_face->glyph;
 
@@ -297,7 +296,7 @@ static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs, const
 		for (FT_ULong character_code = code_min; character_code <= code_max; ++character_code)
 		{
 			FT_UInt index = FT_Get_Char_Index(ft_face, character_code);
-			BuildGlyph(ft_face, index, glyphs, bitmap_scaling_factor);
+			BuildGlyph(ft_face, index, static_cast<Rml::Character>(character_code), glyphs, bitmap_scaling_factor);
 		}
 	}
 
@@ -326,7 +325,8 @@ static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs, const
 			}
 		}
 
-		glyphs[replacement_glyph_index] = std::move(glyph);
+		glyphs[replacement_glyph_index].bitmap = std::move(glyph);
+		glyphs[replacement_glyph_index].character = Rml::Character::Replacement;
 	}
 }
 

+ 1 - 1
Samples/basic/harfbuzz/src/FreeTypeInterface.h

@@ -40,7 +40,7 @@ namespace FreeType {
 bool InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics, bool load_default_glyphs);
 
 // Build a new glyph representing the given glyph index and append to 'glyphs'.
-bool AppendGlyph(FontFaceHandleFreetype face, int font_size, FontGlyphIndex glyph_index, FontGlyphMap& glyphs);
+bool AppendGlyph(FontFaceHandleFreetype face, int font_size, FontGlyphIndex glyph_index, Character character, FontGlyphMap& glyphs);
 
 // Returns the kerning between two characters given by glyph indices.
 // 'font_size' value of zero assumes the font size is already set on the face, and skips this step for performance reasons.

+ 92 - 0
Samples/basic/harfbuzz/src/TextureLayout.cpp

@@ -0,0 +1,92 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "TextureLayout.h"
+#include <algorithm>
+
+struct RectangleSort {
+	bool operator()(const TextureLayoutRectangle& lhs, const TextureLayoutRectangle& rhs) const
+	{
+		return lhs.GetDimensions().y > rhs.GetDimensions().y;
+	}
+};
+
+TextureLayout::TextureLayout() {}
+
+TextureLayout::~TextureLayout() {}
+
+void TextureLayout::AddRectangle(uint64_t id, Vector2i dimensions)
+{
+	rectangles.push_back(TextureLayoutRectangle(id, dimensions));
+}
+
+TextureLayoutRectangle& TextureLayout::GetRectangle(int index)
+{
+	RMLUI_ASSERT(index >= 0);
+	RMLUI_ASSERT(index < GetNumRectangles());
+
+	return rectangles[index];
+}
+
+int TextureLayout::GetNumRectangles() const
+{
+	return (int)rectangles.size();
+}
+
+TextureLayoutTexture& TextureLayout::GetTexture(int index)
+{
+	RMLUI_ASSERT(index >= 0);
+	RMLUI_ASSERT(index < GetNumTextures());
+
+	return textures[index];
+}
+
+int TextureLayout::GetNumTextures() const
+{
+	return (int)textures.size();
+}
+
+bool TextureLayout::GenerateLayout(int max_texture_dimensions)
+{
+	// Sort the rectangles by height.
+	std::sort(rectangles.begin(), rectangles.end(), RectangleSort());
+
+	int num_placed_rectangles = 0;
+	while (num_placed_rectangles != GetNumRectangles())
+	{
+		TextureLayoutTexture texture;
+		int texture_size = texture.Generate(*this, max_texture_dimensions);
+		if (texture_size == 0)
+			return false;
+
+		textures.push_back(texture);
+		num_placed_rectangles += texture_size;
+	}
+
+	return true;
+}

+ 88 - 0
Samples/basic/harfbuzz/src/TextureLayout.h

@@ -0,0 +1,88 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef TEXTSHAPER_TEXTURELAYOUT_H
+#define TEXTSHAPER_TEXTURELAYOUT_H
+
+#include "TextureLayoutRectangle.h"
+#include "TextureLayoutTexture.h"
+#include <RmlUi/Core.h>
+
+/**
+    A texture layout generates and stores a layout of rectangles within a series of textures. It is
+    used primarily by the font system for generating font textures.
+
+	Modified to support 64-bit IDs.
+
+    @author Peter
+ */
+
+using Rml::Vector;
+using Rml::Vector2i;
+
+class TextureLayout {
+public:
+	TextureLayout();
+	~TextureLayout();
+
+	/// Adds a rectangle to the list of rectangles to be laid out. All rectangles must be added to
+	/// the layout before the layout is generated.
+	/// @param[in] id The id of the rectangle; used to identify the rectangle after it has been positioned.
+	/// @param[in] dimensions The dimensions of the rectangle.
+	void AddRectangle(uint64_t id, Vector2i dimensions);
+
+	/// Returns one of the layout's rectangles.
+	/// @param[in] index The index of the desired rectangle.
+	/// @return The desired rectangle.
+	TextureLayoutRectangle& GetRectangle(int index);
+	/// Returns the number of rectangles in the layout.
+	/// @return The layout's rectangle count.
+	int GetNumRectangles() const;
+
+	/// Returns one of the layout's textures.
+	/// @param[in] index The index of the desired texture.
+	/// @return The desired texture.
+	TextureLayoutTexture& GetTexture(int index);
+	/// Returns the number of textures in the layout.
+	/// @return The layout's texture count.
+	int GetNumTextures() const;
+
+	/// Attempts to generate an efficient texture layout for the rectangles.
+	/// @param[in] max_texture_dimensions The maximum dimensions allowed for any single texture.
+	/// @return True if the layout was generated successfully, false if not.
+	bool GenerateLayout(int max_texture_dimensions);
+
+private:
+	using RectangleList = Vector<TextureLayoutRectangle>;
+	using TextureList = Vector<TextureLayoutTexture>;
+
+	TextureList textures;
+	RectangleList rectangles;
+};
+
+#endif

+ 92 - 0
Samples/basic/harfbuzz/src/TextureLayoutRectangle.cpp

@@ -0,0 +1,92 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "TextureLayoutRectangle.h"
+
+TextureLayoutRectangle::TextureLayoutRectangle(const uint64_t _id, const Vector2i dimensions) : dimensions(dimensions), texture_position(0, 0)
+{
+	id = _id;
+	texture_index = -1;
+
+	texture_data = nullptr;
+	texture_stride = 0;
+}
+
+TextureLayoutRectangle::~TextureLayoutRectangle() {}
+
+uint64_t TextureLayoutRectangle::GetId() const
+{
+	return id;
+}
+
+Vector2i TextureLayoutRectangle::GetPosition() const
+{
+	return texture_position;
+}
+
+Vector2i TextureLayoutRectangle::GetDimensions() const
+{
+	return dimensions;
+}
+
+void TextureLayoutRectangle::Place(const int _texture_index, const Vector2i position)
+{
+	texture_index = _texture_index;
+	texture_position = position;
+}
+
+void TextureLayoutRectangle::Unplace()
+{
+	texture_index = -1;
+}
+
+bool TextureLayoutRectangle::IsPlaced() const
+{
+	return texture_index > -1;
+}
+
+void TextureLayoutRectangle::Allocate(byte* _texture_data, int _texture_stride)
+{
+	texture_data = _texture_data + ((texture_position.y * _texture_stride) + texture_position.x * 4);
+	texture_stride = _texture_stride;
+}
+
+int TextureLayoutRectangle::GetTextureIndex()
+{
+	return texture_index;
+}
+
+byte* TextureLayoutRectangle::GetTextureData()
+{
+	return texture_data;
+}
+
+int TextureLayoutRectangle::GetTextureStride() const
+{
+	return texture_stride;
+}

+ 96 - 0
Samples/basic/harfbuzz/src/TextureLayoutRectangle.h

@@ -0,0 +1,96 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef TEXTSHAPER_TEXTURELAYOUTRECTANGLE_H
+#define TEXTSHAPER_TEXTURELAYOUTRECTANGLE_H
+
+#include <RmlUi/Core.h>
+
+using Rml::byte;
+using Rml::Vector2i;
+
+/**
+    A texture layout rectangle is an area positioned with a texture layout.
+	
+	Modified to support 64-bit IDs.
+
+    @author Peter
+ */
+
+class TextureLayoutRectangle {
+public:
+	TextureLayoutRectangle(uint64_t id, Vector2i dimensions);
+	~TextureLayoutRectangle();
+
+	/// Returns the rectangle's id.
+	/// @return The rectangle's id.
+	uint64_t GetId() const;
+	/// Returns the rectangle's position; this is only valid if it has been placed.
+	/// @return The rectangle's position within its texture.
+	Vector2i GetPosition() const;
+	/// Returns the rectangle's dimensions.
+	/// @return The rectangle's dimensions.
+	Vector2i GetDimensions() const;
+
+	/// Places the rectangle within a texture.
+	/// @param[in] texture_index The index of the texture this rectangle is placed on.
+	/// @param[in] position The position within the texture of this rectangle's top-left corner.
+	void Place(int texture_index, Vector2i position);
+	/// Unplaces the rectangle.
+	void Unplace();
+	/// Returns the rectangle's placed state.
+	/// @return True if the rectangle has been placed, false if not.
+	bool IsPlaced() const;
+
+	/// Sets the rectangle's texture data and stride.
+	/// @param[in] texture_data The pointer to the top-left corner of the texture's data.
+	/// @param[in] texture_stride The stride of the texture data, in bytes.
+	void Allocate(byte* texture_data, int texture_stride);
+
+	/// Returns the index of the texture this rectangle is placed on.
+	/// @return The texture index.
+	int GetTextureIndex();
+	/// Returns the rectangle's allocated texture data.
+	/// @return The texture data.
+	byte* GetTextureData();
+	/// Returns the stride of the rectangle's texture data.
+	/// @return The texture data stride.
+	int GetTextureStride() const;
+
+private:
+	uint64_t id;
+	Vector2i dimensions;
+
+	int texture_index;
+	Vector2i texture_position;
+
+	byte* texture_data;
+	int texture_stride;
+};
+
+#endif

+ 98 - 0
Samples/basic/harfbuzz/src/TextureLayoutRow.cpp

@@ -0,0 +1,98 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "TextureLayoutRow.h"
+#include "TextureLayout.h"
+
+TextureLayoutRow::TextureLayoutRow()
+{
+	height = 0;
+}
+
+TextureLayoutRow::~TextureLayoutRow() {}
+
+int TextureLayoutRow::Generate(TextureLayout& layout, int max_width, int y)
+{
+	int width = 1;
+	int first_unplaced_index = 0;
+	int placed_rectangles = 0;
+
+	while (width < max_width)
+	{
+		// Find the first unplaced rectangle we can fit.
+		int index;
+		for (index = first_unplaced_index; index < layout.GetNumRectangles(); ++index)
+		{
+			TextureLayoutRectangle& rectangle = layout.GetRectangle(index);
+			if (!rectangle.IsPlaced())
+			{
+				if (width + rectangle.GetDimensions().x + 1 <= max_width)
+					break;
+			}
+		}
+
+		if (index == layout.GetNumRectangles())
+			return placed_rectangles;
+
+		TextureLayoutRectangle& rectangle = layout.GetRectangle(index);
+
+		// Increment the row height if necessary.
+		height = Rml::Math::Max(height, rectangle.GetDimensions().y);
+
+		// Add this glyph onto our list and mark it as placed.
+		rectangles.push_back(&rectangle);
+		rectangle.Place(layout.GetNumTextures(), Vector2i(width, y));
+		++placed_rectangles;
+
+		// Increment our width. An extra pixel is added on so the rectangles aren't pushed up
+		// against each other. This will avoid filtering artifacts.
+		if (rectangle.GetDimensions().x > 0)
+			width += rectangle.GetDimensions().x + 1;
+
+		first_unplaced_index = index + 1;
+	}
+
+	return placed_rectangles;
+}
+
+void TextureLayoutRow::Allocate(byte* texture_data, int stride)
+{
+	for (size_t i = 0; i < rectangles.size(); ++i)
+		rectangles[i]->Allocate(texture_data, stride);
+}
+
+int TextureLayoutRow::GetHeight() const
+{
+	return height;
+}
+
+void TextureLayoutRow::Unplace()
+{
+	for (size_t i = 0; i < rectangles.size(); ++i)
+		rectangles[i]->Unplace();
+}

+ 79 - 0
Samples/basic/harfbuzz/src/TextureLayoutRow.h

@@ -0,0 +1,79 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef TEXTSHAPER_TEXTURELAYOUTROW_H
+#define TEXTSHAPER_TEXTURELAYOUTROW_H
+
+#include "TextureLayoutRectangle.h"
+#include <RmlUi/Core.h>
+
+using Rml::byte;
+using Rml::Vector;
+
+class TextureLayout;
+
+/**
+    A texture layout row is a single row of rectangles positioned vertically within a texture.
+
+    Modified to support 64-bit IDs.
+
+    @author Peter
+ */
+
+class TextureLayoutRow {
+public:
+	TextureLayoutRow();
+	~TextureLayoutRow();
+
+	/// Attempts to position unplaced rectangles from the layout into this row.
+	/// @param[in] layout The layout to position rectangles from.
+	/// @param[in] width The maximum width of this row.
+	/// @param[in] y The y-coordinate of this row.
+	/// @return The number of placed rectangles.
+	int Generate(TextureLayout& layout, int width, int y);
+
+	/// Assigns allocated texture data to all rectangles in this row.
+	/// @param[in] texture_data The pointer to the beginning of the texture's data.
+	/// @param[in] stride The stride of the texture's surface, in bytes;
+	void Allocate(byte* texture_data, int stride);
+
+	/// Returns the height of the row.
+	/// @return The row's height.
+	int GetHeight() const;
+
+	/// Resets the placed status for all of the rectangles within this row.
+	void Unplace();
+
+private:
+	using RectangleList = Vector<TextureLayoutRectangle*>;
+
+	int height;
+	RectangleList rectangles;
+};
+
+#endif

+ 147 - 0
Samples/basic/harfbuzz/src/TextureLayoutTexture.cpp

@@ -0,0 +1,147 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "TextureLayoutTexture.h"
+#include "TextureDatabase.h"
+#include "TextureLayout.h"
+
+TextureLayoutTexture::TextureLayoutTexture() : dimensions(0, 0) {}
+
+TextureLayoutTexture::~TextureLayoutTexture()
+{
+	// Don't free texture data; freed in the texture loader.
+}
+
+Vector2i TextureLayoutTexture::GetDimensions() const
+{
+	return dimensions;
+}
+
+int TextureLayoutTexture::Generate(TextureLayout& layout, int maximum_dimensions)
+{
+	// Come up with an estimate for how big a texture we need. Calculate the total square pixels
+	// required by the remaining rectangles to place, square-root it to get the dimensions of the
+	// smallest texture necessary (under optimal circumstances) and round it up to the nearest
+	// power of two.
+	int square_pixels = 0;
+	int unplaced_rectangles = 0;
+	for (int i = 0; i < layout.GetNumRectangles(); ++i)
+	{
+		const TextureLayoutRectangle& rectangle = layout.GetRectangle(i);
+
+		if (!rectangle.IsPlaced())
+		{
+			int x = rectangle.GetDimensions().x + 1;
+			int y = rectangle.GetDimensions().y + 1;
+
+			square_pixels += x * y;
+			++unplaced_rectangles;
+		}
+	}
+
+	int texture_width = int(Rml::Math::SquareRoot((float)square_pixels));
+
+	dimensions.y = Rml::Math::ToPowerOfTwo(texture_width);
+	dimensions.x = dimensions.y >> 1;
+
+	dimensions.x = Rml::Math::Min(dimensions.x, maximum_dimensions);
+	dimensions.y = Rml::Math::Min(dimensions.y, maximum_dimensions);
+
+	// Now we're layout out the rectangles in the texture. If we don't fit all the rectangles on
+	// and have room to grow (ie, haven't hit the maximum texture size in both dimensions) then
+	// we'll have another go with a bigger texture.
+	int num_placed_rectangles = 0;
+	for (;;)
+	{
+		bool success = true;
+		int height = 1;
+
+		while (num_placed_rectangles != unplaced_rectangles)
+		{
+			TextureLayoutRow row;
+			int row_size = row.Generate(layout, dimensions.x, height);
+			if (row_size == 0)
+			{
+				success = false;
+				break;
+			}
+
+			height += row.GetHeight() + 1;
+			if (height > dimensions.y)
+			{
+				// D'oh! We've exceeded our height boundaries. This row should be unplaced.
+				row.Unplace();
+				success = false;
+				break;
+			}
+
+			rows.push_back(row);
+			num_placed_rectangles += row_size;
+		}
+
+		// If the rectangles were successfully laid out within the texture limits, we're done.
+		if (success)
+			return num_placed_rectangles;
+
+		// Couldn't do it! Increase the texture size, clear the rectangles and try again - unless
+		// we've hit the maximum texture size, in which case return true if we've placed any
+		// rectangles (ie, the layout isn't empty).
+		if (dimensions.y > dimensions.x)
+			dimensions.x = dimensions.y;
+		else
+		{
+			if (dimensions.y << 1 > maximum_dimensions)
+				return num_placed_rectangles;
+
+			dimensions.y <<= 1;
+		}
+
+		// Unplace all of the glyphs we tried to place and have an other crack.
+		for (size_t i = 0; i < rows.size(); i++)
+			rows[i].Unplace();
+
+		rows.clear();
+		num_placed_rectangles = 0;
+	}
+}
+
+Vector<byte> TextureLayoutTexture::AllocateTexture()
+{
+	Vector<byte> texture_data;
+
+	if (dimensions.x > 0 && dimensions.y > 0)
+	{
+		// Set the texture to transparent black.
+		texture_data.resize(dimensions.x * dimensions.y * 4, 0);
+
+		for (size_t i = 0; i < rows.size(); ++i)
+			rows[i].Allocate(texture_data.data(), dimensions.x * 4);
+	}
+
+	return texture_data;
+}

+ 77 - 0
Samples/basic/harfbuzz/src/TextureLayoutTexture.h

@@ -0,0 +1,77 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef TEXTSHAPER_TEXTURELAYOUTTEXTURE_H
+#define TEXTSHAPER_TEXTURELAYOUTTEXTURE_H
+
+#include "TextureLayoutRow.h"
+#include <RmlUi/Core.h>
+
+using Rml::byte;
+using Rml::Vector;
+
+class TextureLayout;
+
+/**
+    A texture layout texture is a single rectangular area which sub-rectangles are placed on within
+    a complete texture layout.
+
+	Modified to support 64-bit IDs.
+
+    @author Peter
+ */
+
+class TextureLayoutTexture {
+public:
+	TextureLayoutTexture();
+	~TextureLayoutTexture();
+
+	/// Returns the texture's dimensions. This is only valid after the texture has been generated.
+	/// @return The texture's dimensions.
+	Vector2i GetDimensions() const;
+
+	/// Attempts to position unplaced rectangles from the layout into this texture. The size of
+	/// this texture will be determined by its contents.
+	/// @param[in] layout The layout to position rectangles from.
+	/// @param[in] maximum_dimensions The maximum dimensions of this texture. If this is not big enough to place all the rectangles, then as many will
+	/// be placed as possible.
+	/// @return The number of placed rectangles.
+	int Generate(TextureLayout& layout, int maximum_dimensions);
+
+	/// Allocates the texture.
+	/// @return The allocated texture data.
+	Vector<byte> AllocateTexture();
+
+private:
+	using RowList = Vector<TextureLayoutRow>;
+
+	Vector2i dimensions;
+	RowList rows;
+};
+
+#endif