Browse Source

Add glyphs from fallback fonts to the current font face. This fixes font-effects on glyphs from the fallback font, and makes things generally simpler possibly at the expense of some larger texture sizes.

Michael Ragazzon 6 years ago
parent
commit
0aacb9f68d

+ 17 - 3
Include/RmlUi/Core/FontGlyph.h

@@ -43,8 +43,7 @@ namespace Core {
 class FontGlyph
 {
 public:
-	FontGlyph() : dimensions(0,0), bearing(0,0), advance(0), bitmap_data(nullptr),
-		bitmap_dimensions(0,0)
+	FontGlyph() : dimensions(0,0), bearing(0,0), advance(0), bitmap_data(nullptr), bitmap_dimensions(0,0)
 	{}
 
 	/// The glyph's bounding box. Not to be confused with the dimensions of the glyph's bitmap!
@@ -58,9 +57,24 @@ public:
 
 	/// 8-bit opacity information for the glyph's bitmap. The size of the data is given by the
 	/// dimensions, below. This will be nullptr if the glyph has no bitmap data.
-	UniquePtr<byte[]> bitmap_data;
+	const byte* bitmap_data;
 	/// The dimensions of the glyph's bitmap.
 	Vector2i bitmap_dimensions;
+
+	// Bitmap_data may point to this member or another font glyph data.
+	UniquePtr<byte[]> bitmap_owned_data;
+
+	// Create a copy with its bitmap data owned by another glyph.
+	FontGlyph WeakCopy() const 
+	{
+		FontGlyph glyph;
+		glyph.dimensions = dimensions;
+		glyph.bearing = bearing;
+		glyph.advance = advance;
+		glyph.bitmap_data = bitmap_data;
+		glyph.bitmap_dimensions = bitmap_dimensions;
+		return glyph;
+	}
 };
 
 using FontGlyphMap = UnorderedMap<CodePoint, FontGlyph>;

+ 2 - 1
Samples/basic/demo/data/demo.rml

@@ -120,12 +120,13 @@ button:focus {
 {
 	/*font-family: Noto Emoji;*/
 	font-size: 35px;
+	color: #b33;
 }
 
 textarea {
 	font-size: 18px;
 	font-effect: outline(2px #006600);
-	color: #333;
+	color: #ddd;
 	
 }
 </style>

+ 1 - 1
Source/Core/FontEffectOutline.cpp

@@ -99,7 +99,7 @@ bool FontEffectOutline::GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions,
 // Expands the original glyph texture for the outline.
 void FontEffectOutline::GenerateGlyphTexture(byte* destination_data, const Vector2i& destination_dimensions, int destination_stride, const FontGlyph& glyph) const
 {
-	filter.Run(destination_data, destination_dimensions, destination_stride, glyph.bitmap_data.get(), glyph.bitmap_dimensions, Vector2i(width, width));
+	filter.Run(destination_data, destination_dimensions, destination_stride, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width, width));
 }
 
 }

+ 0 - 1
Source/Core/FontEngineInterfaceDefault.cpp

@@ -115,7 +115,6 @@ int FontEngineInterfaceDefault::GenerateString(FontFaceHandle handle, GeometryLi
 int FontEngineInterfaceDefault::GetVersion(FontFaceHandle handle)
 {
 	auto handle_default = reinterpret_cast<FontFaceHandleDefault*>(handle);
-	handle_default->UpdateLayersOnDirty();
 	return handle_default->GetVersion();
 }
 

+ 65 - 103
Source/Core/FontFaceHandleDefault.cpp

@@ -43,7 +43,6 @@ FontFaceHandleDefault::FontFaceHandleDefault()
 {
 	metrics = {};
 	base_layer = nullptr;
-	fallback_face = nullptr;
 }
 
 FontFaceHandleDefault::~FontFaceHandleDefault()
@@ -104,7 +103,7 @@ int FontFaceHandleDefault::GetStringWidth(const String& string, CodePoint prior_
 	{
 		CodePoint code_point = *it_string;
 
-		const FontGlyph* glyph = GetOrAppendGlyph(code_point, nullptr);
+		const FontGlyph* glyph = GetOrAppendGlyph(code_point);
 		if (!glyph)
 			continue;
 
@@ -173,7 +172,7 @@ int FontFaceHandleDefault::GenerateLayerConfiguration(const FontEffectList& font
 			added_base_layer = true;
 		}
 
-		FontFaceLayer* new_layer = GenerateLayer(font_effects[i]);
+		FontFaceLayer* new_layer = GetOrCreateLayer(font_effects[i]);
 		layer_configuration.push_back(new_layer);
 	}
 
@@ -206,23 +205,13 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String&
 	RMLUI_ASSERT(layer_configuration_index >= 0);
 	RMLUI_ASSERT(layer_configuration_index < (int) layer_configurations.size());
 
+	UpdateLayersOnDirty();
+
 	// Fetch the requested configuration and generate the geometry for each one.
 	const LayerConfiguration& layer_configuration = layer_configurations[layer_configuration_index];
-	for (size_t i = 0; i <= layer_configuration.size(); ++i)
+	for (size_t i = 0; i < layer_configuration.size(); ++i)
 	{
-		FontFaceLayer* layer = nullptr;
-		if (i < layer_configuration.size())
-		{
-			layer = layer_configuration[i];
-		}
-		else if (fallback_face)
-		{
-			fallback_face->UpdateLayersOnDirty();
-			layer = fallback_face->base_layer;
-		}
-
-		if (!layer)
-			continue;
+		FontFaceLayer* layer = layer_configuration[i];
 
 		Colourb layer_colour;
 		if (layer == base_layer)
@@ -249,7 +238,7 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String&
 		for (auto it_string = StringIteratorU8(string); it_string; ++it_string)
 		{
 			CodePoint code_point = *it_string;
-			bool located_in_fallback_font = false;
+			bool located_in_fallback_font = false; // TODO
 
 			const FontGlyph* glyph = GetOrAppendGlyph(code_point, &located_in_fallback_font);
 			if (!glyph)
@@ -259,10 +248,7 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String&
 			if (prior_character != CodePoint::Null)
 				line_width += GetKerning(prior_character, code_point);
 
-			if(!fallback_face || (located_in_fallback_font == (layer == fallback_face->base_layer)))
-			{
-				layer->GenerateGeometry(&geometry[geometry_index], code_point, Vector2f(position.x + line_width, position.y), layer_colour);
-			}
+			layer->GenerateGeometry(&geometry[geometry_index], code_point, Vector2f(position.x + line_width, position.y), layer_colour);
 
 			line_width += glyph->advance;
 			prior_character = code_point;
@@ -279,57 +265,22 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String&
 
 bool FontFaceHandleDefault::UpdateLayersOnDirty()
 {
-	bool result = is_layers_dirty;
+	bool result = false;
 
-	if(is_layers_dirty)
+	// If we are dirty, regenerate all the layers and increment the version
+	if(is_layers_dirty && base_layer)
 	{
 		is_layers_dirty = false;
 		++version;
 
-		// If we are dirty, regenerate the base layer and increment the version
-		if (base_layer)
+		// Regenerate all the layers
+		for (auto& pair : layers)
 		{
-			// Regenerate all the layers
-			// TODO: The following is almost a copy-paste of GenerateLayer.
-
-			for (auto& pair : layers)
-			{
-				const FontEffect* font_effect = pair.first;
-				FontFaceLayer* layer = pair.second.get();
-
-				if (!font_effect)
-				{
-					layer->Regenerate(this);
-				}
-				else
-				{
-					// Determine which, if any, layer the new layer should copy its geometry and textures from.
-					FontFaceLayer* clone = nullptr;
-					bool clone_glyph_origins = true;
-					String generation_key;
-					size_t fingerprint = font_effect->GetFingerprint();
-
-					if (!font_effect->HasUniqueTexture())
-					{
-						clone = base_layer;
-						clone_glyph_origins = false;
-					}
-					else
-					{
-						auto cache_iterator = layer_cache.find(fingerprint);
-						if (cache_iterator != layer_cache.end() && cache_iterator->second != layer)
-							clone = cache_iterator->second;
-					}
-
-					// Create a new layer.
-					layer->Regenerate(this, clone, clone_glyph_origins);
-
-					// Cache the layer in the layer cache if it generated its own textures (ie, didn't clone).
-					if (!clone)
-						layer_cache[fingerprint] = layer;
-				}
-			}
+			FontFaceLayer* layer = pair.second.get();
+			GenerateLayer(layer);
 		}
+
+		result = true;
 	}
 
 	return result;
@@ -350,16 +301,13 @@ FontMetrics& FontFaceHandleDefault::GetMetrics() {
 
 void FontFaceHandleDefault::GenerateBaseLayer()
 {
-	RMLUI_ASSERTMSG(layer_configurations.empty(), "This should only be called before any layers are generated.");
-	base_layer = GenerateLayer(nullptr);
+	RMLUI_ASSERTMSG(layer_configurations.empty(), "GenerateBaseLayer should only be called before any layers are generated.");
+	base_layer = GetOrCreateLayer(nullptr);
 	layer_configurations.push_back(LayerConfiguration{ base_layer });
 }
 
-const FontGlyph* FontFaceHandleDefault::GetOrAppendGlyph(CodePoint& code_point, bool* located_in_fallback_font, bool look_in_fallback_fonts)
+const FontGlyph* FontFaceHandleDefault::GetOrAppendGlyph(CodePoint& code_point, bool look_in_fallback_fonts)
 {
-	if (located_in_fallback_font)
-		*located_in_fallback_font = false;
-
 	// Don't try to render control characters
 	if ((unsigned int)code_point < (unsigned int)' ')
 		return nullptr;
@@ -382,33 +330,33 @@ const FontGlyph* FontFaceHandleDefault::GetOrAppendGlyph(CodePoint& code_point,
 		}
 		else if (look_in_fallback_fonts)
 		{
-			if (!fallback_face)
+			const int num_fallback_faces = FontDatabaseDefault::CountFallbackFontFaces();
+			for (int i = 0; i < num_fallback_faces; i++)
 			{
-				// TODO: Only support for single fallback face
-				fallback_face = FontDatabaseDefault::GetFallbackFontFace(0, metrics.size).get();
+				FontFaceHandleDefault* fallback_face = FontDatabaseDefault::GetFallbackFontFace(i, metrics.size).get();
 				if (fallback_face == this)
-					fallback_face = nullptr;
-			}
+					continue;
 
-			if (fallback_face)
-			{
-				const FontGlyph* glyph = fallback_face->GetOrAppendGlyph(code_point, nullptr, false);
+				const FontGlyph* glyph = fallback_face->GetOrAppendGlyph(code_point, false);
 				if (glyph)
 				{
-					//is_layers_dirty = true;
-					//++version;
-
-					if (located_in_fallback_font)
-						*located_in_fallback_font = true;
-
-					return glyph;
+					// Insert the new glyph into our own set of glyphs
+					auto pair = glyphs.emplace(code_point, glyph->WeakCopy());
+					it_glyph = pair.first;
+					if(pair.second)
+						is_layers_dirty = true;
+					break;
 				}
 			}
 
-			code_point = CodePoint::Replacement;
-			it_glyph = glyphs.find(code_point);
-			if (it_glyph == glyphs.end())
-				return nullptr;
+			// If we still have not found a glyph, use the replacement character.
+			if(it_glyph == glyphs.end())
+			{
+				code_point = CodePoint::Replacement;
+				it_glyph = glyphs.find(code_point);
+				if (it_glyph == glyphs.end())
+					return nullptr;
+			}
 		}
 		else
 		{
@@ -421,19 +369,33 @@ const FontGlyph* FontFaceHandleDefault::GetOrAppendGlyph(CodePoint& code_point,
 }
 
 // Generates (or shares) a layer derived from a font effect.
-FontFaceLayer* FontFaceHandleDefault::GenerateLayer(const SharedPtr<const FontEffect>& font_effect)
+FontFaceLayer* FontFaceHandleDefault::GetOrCreateLayer(const SharedPtr<const FontEffect>& font_effect)
 {
-	// See if this effect has been instanced before, as part of a different configuration.
-	FontLayerMap::iterator i = layers.find(font_effect.get());
-	if (i != layers.end())
-		return i->second.get();
+	// Try inserting the font effect, it may have been instanced before as part of a different configuration.
+	auto pair = layers.emplace(font_effect.get(), nullptr);
 
-	auto& layer = layers[font_effect.get()];
-	layer = std::make_unique<FontFaceLayer>();
+	bool inserted = pair.second;
+	auto& layer = pair.first->second;
+
+	if (!inserted)
+		return layer.get();
+
+	// The new effect was inserted, generate a new layer.
+	layer = std::make_unique<FontFaceLayer>(font_effect);
+	GenerateLayer(layer.get());
+
+	return layer.get();
+}
+
+bool FontFaceHandleDefault::GenerateLayer(FontFaceLayer* layer)
+{
+	RMLUI_ASSERT(layer);
+	const FontEffect* font_effect = layer->GetFontEffect();
+	bool result = false;
 
 	if (!font_effect)
 	{
-		layer->Initialise(this);
+		result = layer->Generate(this, nullptr, false);
 	}
 	else
 	{
@@ -451,19 +413,19 @@ FontFaceLayer* FontFaceHandleDefault::GenerateLayer(const SharedPtr<const FontEf
 		else
 		{
 			auto cache_iterator = layer_cache.find(fingerprint);
-			if (cache_iterator != layer_cache.end())
+			if (cache_iterator != layer_cache.end() && cache_iterator->second != layer)
 				clone = cache_iterator->second;
 		}
 
 		// Create a new layer.
-		layer->Initialise(this, font_effect, clone, clone_glyph_origins);
+		result = layer->Generate(this, clone, clone_glyph_origins);
 
 		// Cache the layer in the layer cache if it generated its own textures (ie, didn't clone).
 		if (!clone)
-			layer_cache[fingerprint] = layer.get();
+			layer_cache[fingerprint] = layer;
 	}
 
-	return layer.get();
+	return result;
 }
 
 #endif

+ 8 - 4
Source/Core/FontFaceHandleDefault.h

@@ -117,7 +117,6 @@ public:
 	/// @return The width, in pixels, of the string geometry.
 	int GenerateString(GeometryList& geometry, const String& string, const Vector2f& position, const Colourb& colour, int layer_configuration = 0);
 
-	bool UpdateLayersOnDirty();
 
 	int GetVersion() const;
 
@@ -135,10 +134,16 @@ protected:
 
 private:
 
+	bool UpdateLayersOnDirty();
+
 	// Note: can modify code_point to change character to e.g. replacement character.
-	const FontGlyph* GetOrAppendGlyph(CodePoint& code_point, bool* located_in_fallback_font, bool look_in_fallback_fonts = true);
+	const FontGlyph* GetOrAppendGlyph(CodePoint& code_point, bool look_in_fallback_fonts = true);
+
+	// Create a new layer from the given font effect if it does not already exist.
+	FontFaceLayer* GetOrCreateLayer(const SharedPtr<const FontEffect>& font_effect);
 
-	FontFaceLayer* GenerateLayer(const SharedPtr<const FontEffect>& font_effect);
+	// (Re-)generate a layer in this font face handle.
+	bool GenerateLayer(FontFaceLayer* layer);
 
 	FontGlyphMap glyphs;
 
@@ -152,7 +157,6 @@ private:
 	FontLayerMap layers;
 	// Each font layer that generated geometry or textures, indexed by the respective generation key.
 	FontLayerCache layer_cache;
-	FontFaceHandleDefault* fallback_face;
 
 	int version = 0;
 	bool is_layers_dirty = false;

+ 20 - 35
Source/Core/FontFaceLayer.cpp

@@ -37,50 +37,36 @@ namespace Core {
 
 #ifndef RMLUI_NO_FONT_INTERFACE_DEFAULT
 
-FontFaceLayer::FontFaceLayer() : colour(255, 255, 255)
-{}
-
-FontFaceLayer::~FontFaceLayer()
-{
-}
-
-// Generates the character and texture data for the layer.
-bool FontFaceLayer::Initialise(const FontFaceHandleDefault* handle, SharedPtr<const FontEffect> _effect, const FontFaceLayer* clone, bool clone_glyph_origins)
+FontFaceLayer::FontFaceLayer(const SharedPtr<const FontEffect>& _effect) : colour(255, 255, 255)
 {
 	effect = _effect;
 	if (effect)
 		colour = effect->GetColour();
-
-	bool result = GenerateLayout(handle, clone, clone_glyph_origins);
-
-	return result;
 }
 
-bool FontFaceLayer::Regenerate(const FontFaceHandleDefault* handle, const FontFaceLayer* clone, bool clone_glyph_origins)
-{
-	// @performance: We could be much smarter about this, e.g. such as adding new glyphs to the existing texture layout and textures.
-	// Right now we re-generate the whole thing, including textures.
-
-	for (auto& texture : textures)
-		texture.RemoveDatabaseCache();
-
-	texture_layout = TextureLayout{};
-	characters.clear();
-	textures.clear();
-
-	bool result = GenerateLayout(handle, clone, clone_glyph_origins);
-
-	return result;
-}
+FontFaceLayer::~FontFaceLayer()
+{}
 
-bool FontFaceLayer::GenerateLayout(const FontFaceHandleDefault* handle, const FontFaceLayer* clone, bool clone_glyph_origins)
+bool FontFaceLayer::Generate(const FontFaceHandleDefault* handle, const FontFaceLayer* clone, bool clone_glyph_origins)
 {
+	// Clear the old layout if it exists.
+	{
+		// @performance: We could be much smarter about this, e.g. such as adding new glyphs to the existing texture layout and textures.
+		// Right now we re-generate the whole thing, including textures.
+		for (auto& texture : textures)
+			texture.RemoveDatabaseCache();
+
+		texture_layout = TextureLayout{};
+		characters.clear();
+		textures.clear();
+	}
+
 	const FontGlyphMap& glyphs = handle->GetGlyphs();
 
-	// Clone the geometry and textures from the clone layer.
+	// Generate the new layout.
 	if (clone)
 	{
-		// Copy the cloned layer's characters.
+		// Clone the geometry and textures from the clone layer.
 		characters = clone->characters;
 
 		// Copy the cloned layer's textures.
@@ -182,7 +168,6 @@ bool FontFaceLayer::GenerateLayout(const FontFaceHandleDefault* handle, const Fo
 
 
 	return true;
-	return false;
 }
 
 // Generates the texture data for a layer (for the texture database).
@@ -216,10 +201,10 @@ bool FontFaceLayer::GenerateTexture(const FontGlyphMap& glyphs, UniquePtr<const
 		if (effect == nullptr)
 		{
 			// Copy the glyph's bitmap data into its allocated texture.
-			if (glyph.bitmap_data != nullptr)
+			if (glyph.bitmap_data)
 			{
 				byte* destination = rectangle.GetTextureData();
-				const byte* source = glyph.bitmap_data.get();
+				const byte* source = glyph.bitmap_data;
 
 				for (int j = 0; j < glyph.bitmap_dimensions.y; ++j)
 				{

+ 3 - 8
Source/Core/FontFaceLayer.h

@@ -53,18 +53,15 @@ class FontFaceHandleDefault;
 class FontFaceLayer
 {
 public:
-	FontFaceLayer();
+	FontFaceLayer(const SharedPtr<const FontEffect>& _effect);
 	~FontFaceLayer();
 
-	/// Generates the character and texture data for the layer.
+	/// Generates or re-generates the character and texture data for the layer.
 	/// @param[in] handle The handle generating this layer.
 	/// @param[in] effect The effect to initialise the layer with.
 	/// @param[in] clone The layer to optionally clone geometry and texture data from.
 	/// @return True if the layer was generated successfully, false if not.
-	bool Initialise(const FontFaceHandleDefault* handle, SharedPtr<const FontEffect> effect = {}, const FontFaceLayer* clone = nullptr, bool clone_glyph_origins = false);
-
-	/// Clears and re-initalize the font face layer, using the font effect it was first initialized with, such as to add new glyphs to the layer.
-	bool Regenerate(const FontFaceHandleDefault* handle, const FontFaceLayer* clone = nullptr, bool clone_glyph_origins = false);
+	bool Generate(const FontFaceHandleDefault* handle, const FontFaceLayer* clone = nullptr, bool clone_glyph_origins = false);
 
 	/// Generates the texture data for a layer (for the texture database).
 	/// @param[out] texture_data The pointer to be set to the generated texture data.
@@ -119,8 +116,6 @@ public:
 
 private:
 
-	bool GenerateLayout(const FontFaceHandleDefault* handle, const FontFaceLayer* clone, bool clone_glyph_origins);
-
 
 	struct Character
 	{

+ 9 - 5
Source/Core/FreeType/FontFaceHandle.cpp

@@ -86,7 +86,7 @@ bool FontFaceHandle_FreeType::AppendGlyph(CodePoint code_point)
 	RMLUI_ASSERT(glyphs.find(code_point) == glyphs.end());
 	RMLUI_ASSERT(ft_face);
 
-	// TODO: Why is this needed ??
+	// Set face size again in case it was used at another size in another font face handle.
 	FT_Error error = FT_Set_Char_Size(ft_face, 0, GetMetrics().size << 6, 0, 0);
 	if (error != 0)
 	{
@@ -131,7 +131,8 @@ static void BuildGlyphMap(FT_Face ft_face, FontGlyphMap& glyphs, int size)
 		glyph.advance = glyph.dimensions.x + 2;
 		glyph.bearing = { 1, glyph.dimensions.y };
 
-		glyph.bitmap_data.reset(new byte[glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y]);
+		glyph.bitmap_owned_data.reset(new byte[glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y]);
+		glyph.bitmap_data = glyph.bitmap_owned_data.get();
 
 		for (int y = 0; y < glyph.bitmap_dimensions.y; y++)
 		{
@@ -140,7 +141,7 @@ static void BuildGlyphMap(FT_Face ft_face, FontGlyphMap& glyphs, int size)
 				constexpr int stroke = 1;
 				int i = y * glyph.bitmap_dimensions.x + x;
 				bool near_edge = (x < stroke || x >= glyph.bitmap_dimensions.x - stroke || y < stroke || y >= glyph.bitmap_dimensions.y - stroke);
-				glyph.bitmap_data[i] = (near_edge ? 0xdd : 0);
+				glyph.bitmap_owned_data[i] = (near_edge ? 0xdd : 0);
 			}
 		}
 
@@ -201,15 +202,17 @@ static bool BuildGlyph(FT_Face ft_face, CodePoint code_point, FontGlyphMap& glyp
 		if (ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_MONO &&
 			ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
 		{
+			glyph.bitmap_owned_data.reset();
 			glyph.bitmap_data = nullptr;
 			Log::Message(Log::LT_WARNING, "Unable to render glyph on the font face '%s %s'; unsupported pixel mode (%d).", ft_glyph->face->family_name, ft_glyph->face->style_name, ft_glyph->bitmap.pixel_mode);
 		}
 		else
 		{
-			glyph.bitmap_data.reset(new byte[glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y]);
+			glyph.bitmap_owned_data.reset(new byte[glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y]);
+			glyph.bitmap_data = glyph.bitmap_owned_data.get();
 
 			const byte* source_bitmap = ft_glyph->bitmap.buffer;
-			byte* destination_bitmap = glyph.bitmap_data.get();
+			byte* destination_bitmap = glyph.bitmap_owned_data.get();
 
 			// Copy the bitmap data into the newly-allocated space on our glyph.
 			switch (ft_glyph->bitmap.pixel_mode)
@@ -258,6 +261,7 @@ static bool BuildGlyph(FT_Face ft_face, CodePoint code_point, FontGlyphMap& glyp
 	}
 	else
 	{
+		glyph.bitmap_owned_data.reset();
 		glyph.bitmap_data = nullptr;
 	}