Browse Source

Allow numeric values in the 'font-weight' property, and enable loading fonts with multiple font-weights.

updated font-weight

fixes for different os compilers

Requested changes.

small fix

accidently left an include line in

requested changes 2

Add additional named weights to FontWeight, but only allow 'normal' and 'bold' in RCSS for compatibility with CSS.

Make memory for font faces owned by their font family, prevents use-after-free.

Minor comment

Change weight_to_load parameter back to weight

Font-weights refactoring and additions:
- Font files with a single face now loads correctly again.
- Only a single face will be loaded for a given font-weight value even when there are multiple width variations (choosing the width closest to 'regular' width).
- Slightly changed the purpose of the 'weight' parameter, it should now be backwards compatible with previous usage.
- No longer need to search the font variations twice, passing in the named style to the load face function.

Add missing header

Replace custom parser for the 'font-weight' property with generic parsers

Remove non-standard enum values from FontWeight

Co-authored-by: Michael Ragazzon <[email protected]>
Mex 3 years ago
parent
commit
477631acfa

+ 1 - 1
Include/RmlUi/Core/ComputedValues.h

@@ -114,7 +114,7 @@ public:
 enum class Visibility : uint8_t { Visible, Hidden };
 enum class Visibility : uint8_t { Visible, Hidden };
 
 
 enum class FontStyle : uint8_t { Normal, Italic };
 enum class FontStyle : uint8_t { Normal, Italic };
-enum class FontWeight : uint8_t { Normal, Bold };
+enum class FontWeight : uint16_t { Auto = 0, Normal = 400, Bold = 700 }; // Any definite value in the range [1,1000] is valid.
 
 
 enum class TextAlign : uint8_t { Left, Right, Center, Justify };
 enum class TextAlign : uint8_t { Left, Right, Center, Justify };
 enum class TextDecoration : uint8_t { None, Underline, Overline, LineThrough };
 enum class TextDecoration : uint8_t { None, Underline, Overline, LineThrough };

+ 7 - 3
Include/RmlUi/Core/Core.h

@@ -119,18 +119,22 @@ RMLUICORE_API int GetNumContexts();
 /// Adds a new font face to the font engine. The face's family, style and weight will be determined from the face itself.
 /// Adds a new font face to the font engine. The face's family, style and weight will be determined from the face itself.
 /// @param[in] file_name The file to load the face from.
 /// @param[in] file_name The file to load the face from.
 /// @param[in] fallback_face True to use this font face for unknown characters in other font faces.
 /// @param[in] fallback_face True to use this font face for unknown characters in other font faces.
+/// @param[in] weight The weight to load when the font face contains multiple weights, otherwise the weight to register the font as. By default it
+/// loads all found font weights.
 /// @return True if the face was loaded successfully, false otherwise.
 /// @return True if the face was loaded successfully, false otherwise.
-RMLUICORE_API bool LoadFontFace(const String& file_name, bool fallback_face = false);
+RMLUICORE_API bool LoadFontFace(const String& file_name, bool fallback_face = false, Style::FontWeight weight = Style::FontWeight::Auto);
 /// Adds a new font face from memory to the font engine. The face's family, style and weight is given by the parameters.
 /// Adds a new font face from memory to the font engine. The face's family, style and weight is given by the parameters.
 /// @param[in] data A pointer to the data.
 /// @param[in] data A pointer to the data.
 /// @param[in] data_size Size of the data in bytes.
 /// @param[in] data_size Size of the data in bytes.
 /// @param[in] family The family to register the font as.
 /// @param[in] family The family to register the font as.
 /// @param[in] style The style to register the font as.
 /// @param[in] style The style to register the font as.
-/// @param[in] weight The weight to register the font as.
+/// @param[in] weight The weight to load when the font face contains multiple weights, otherwise the weight to register the font as. By default it
+/// loads all found font weights.
 /// @param[in] fallback_face True to use this font face for unknown characters in other font faces.
 /// @param[in] fallback_face True to use this font face for unknown characters in other font faces.
 /// @return True if the face was loaded successfully, false otherwise.
 /// @return True if the face was loaded successfully, false otherwise.
 /// @lifetime The pointed to 'data' must remain available until after the call to Rml::Shutdown.
 /// @lifetime The pointed to 'data' must remain available until after the call to Rml::Shutdown.
-RMLUICORE_API bool LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face = false);
+RMLUICORE_API bool LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style,
+	Style::FontWeight weight = Style::FontWeight::Auto, bool fallback_face = false);
 
 
 /// Registers a generic RmlUi plugin.
 /// Registers a generic RmlUi plugin.
 RMLUICORE_API void RegisterPlugin(Plugin* plugin);
 RMLUICORE_API void RegisterPlugin(Plugin* plugin);

+ 3 - 2
Include/RmlUi/Core/FontEngineInterface.h

@@ -52,15 +52,16 @@ public:
 	/// Called by RmlUi when it wants to load a font face from file.
 	/// Called by RmlUi when it wants to load a font face from file.
 	/// @param[in] file_name The file to load the face from.
 	/// @param[in] file_name The file to load the face from.
 	/// @param[in] fallback_face True to use this font face for unknown characters in other font faces.
 	/// @param[in] fallback_face True to use this font face for unknown characters in other font faces.
+	/// @param[in] weight The weight to load when the font face contains multiple weights, otherwise the weight to register the font as.
 	/// @return True if the face was loaded successfully, false otherwise.
 	/// @return True if the face was loaded successfully, false otherwise.
-	virtual bool LoadFontFace(const String& file_name, bool fallback_face);
+	virtual bool LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight);
 
 
 	/// Called by RmlUi when it wants to load a font face from memory, registered using the provided family, style, and weight.
 	/// Called by RmlUi when it wants to load a font face from memory, registered using the provided family, style, and weight.
 	/// @param[in] data A pointer to the data.
 	/// @param[in] data A pointer to the data.
 	/// @param[in] data_size Size of the data in bytes.
 	/// @param[in] data_size Size of the data in bytes.
 	/// @param[in] family The family to register the font as.
 	/// @param[in] family The family to register the font as.
 	/// @param[in] style The style to register the font as.
 	/// @param[in] style The style to register the font as.
-	/// @param[in] weight The weight to register the font as.
+	/// @param[in] weight The weight to load when the font face contains multiple weights, otherwise the weight to register the font as.
 	/// @param[in] fallback_face True to use this font face for unknown characters in other font faces.
 	/// @param[in] fallback_face True to use this font face for unknown characters in other font faces.
 	/// @return True if the face was loaded successfully, false otherwise.
 	/// @return True if the face was loaded successfully, false otherwise.
 	/// Note: The debugger plugin will load its embedded font faces through this method using the family name 'rmlui-debugger-font'.
 	/// Note: The debugger plugin will load its embedded font faces through this method using the family name 'rmlui-debugger-font'.

+ 1 - 1
Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp

@@ -40,7 +40,7 @@ FontEngineInterfaceBitmap::~FontEngineInterfaceBitmap()
 	FontProviderBitmap::Shutdown();
 	FontProviderBitmap::Shutdown();
 }
 }
 
 
-bool FontEngineInterfaceBitmap::LoadFontFace(const String& file_name, bool /*fallback_face*/)
+bool FontEngineInterfaceBitmap::LoadFontFace(const String& file_name, bool /*fallback_face*/, FontWeight /*weight*/)
 {
 {
 	return FontProviderBitmap::LoadFontFace(file_name);
 	return FontProviderBitmap::LoadFontFace(file_name);
 }
 }

+ 1 - 1
Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h

@@ -57,7 +57,7 @@ public:
 	virtual ~FontEngineInterfaceBitmap();
 	virtual ~FontEngineInterfaceBitmap();
 
 
 	/// Called by RmlUi when it wants to load a font face from file.
 	/// Called by RmlUi when it wants to load a font face from file.
-	bool LoadFontFace(const String& file_name, bool fallback_face) override;
+	bool LoadFontFace(const String& file_name, bool fallback_face, FontWeight weight) override;
 
 
 	/// Called by RmlUi when it wants to load a font face from memory, registered using the provided family, style, and weight.
 	/// Called by RmlUi when it wants to load a font face from memory, registered using the provided family, style, and weight.
 	/// @param[in] data A pointer to the data.
 	/// @param[in] data A pointer to the data.

+ 2 - 2
Source/Core/Core.cpp

@@ -331,9 +331,9 @@ int GetNumContexts()
 	return (int) contexts.size();
 	return (int) contexts.size();
 }
 }
 
 
-bool LoadFontFace(const String& file_name, bool fallback_face)
+bool LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight)
 {
 {
-	return font_interface->LoadFontFace(file_name, fallback_face);
+	return font_interface->LoadFontFace(file_name, fallback_face, weight);
 }
 }
 
 
 bool LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face)
 bool LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face)

+ 2 - 2
Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp

@@ -42,9 +42,9 @@ FontEngineInterfaceDefault::~FontEngineInterfaceDefault()
 	FontProvider::Shutdown();
 	FontProvider::Shutdown();
 }
 }
 
 
-bool FontEngineInterfaceDefault::LoadFontFace(const String& file_name, bool fallback_face)
+bool FontEngineInterfaceDefault::LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight)
 {
 {
-	return FontProvider::LoadFontFace(file_name, fallback_face);
+	return FontProvider::LoadFontFace(file_name, fallback_face, weight);
 }
 }
 
 
 bool FontEngineInterfaceDefault::LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face)
 bool FontEngineInterfaceDefault::LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face)

+ 1 - 1
Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h

@@ -39,7 +39,7 @@ public:
 	virtual ~FontEngineInterfaceDefault();
 	virtual ~FontEngineInterfaceDefault();
 
 
 	/// Adds a new font face to the database. The face's family, style and weight will be determined from the face itself.
 	/// Adds a new font face to the database. The face's family, style and weight will be determined from the face itself.
-	bool LoadFontFace(const String& file_name, bool fallback_face) override;
+	bool LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight) override;
 
 
 	/// Adds a new font face to the database using the provided family, style and weight.
 	/// Adds a new font face to the database using the provided family, style and weight.
 	bool LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face) override;
 	bool LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face) override;

+ 1 - 8
Source/Core/FontEngineDefault/FontFace.cpp

@@ -33,24 +33,17 @@
 
 
 namespace Rml {
 namespace Rml {
 
 
-FontFace::FontFace(FontFaceHandleFreetype _face, Style::FontStyle _style, Style::FontWeight _weight, UniquePtr<byte[]> _face_memory)
+FontFace::FontFace(FontFaceHandleFreetype _face, Style::FontStyle _style, Style::FontWeight _weight)
 {
 {
 	style = _style;
 	style = _style;
 	weight = _weight;
 	weight = _weight;
 	face = _face;
 	face = _face;
-
-	face_memory = std::move(_face_memory);
 }
 }
 
 
 FontFace::~FontFace()
 FontFace::~FontFace()
 {
 {
 	if (face) 
 	if (face) 
-	{
 		FreeType::ReleaseFace(face);
 		FreeType::ReleaseFace(face);
-		face_memory.reset();
-		face = 0;
-	}
-	handles.clear();
 }
 }
 
 
 // Returns the style of the font face.
 // Returns the style of the font face.

+ 1 - 4
Source/Core/FontEngineDefault/FontFace.h

@@ -43,7 +43,7 @@ class FontFaceHandleDefault;
 class FontFace
 class FontFace
 {
 {
 public:
 public:
-	FontFace(FontFaceHandleFreetype face, Style::FontStyle style, Style::FontWeight weight, UniquePtr<byte[]> face_memory);
+	FontFace(FontFaceHandleFreetype face, Style::FontStyle style, Style::FontWeight weight);
 	~FontFace();
 	~FontFace();
 
 
 	Style::FontStyle GetStyle() const;
 	Style::FontStyle GetStyle() const;
@@ -59,9 +59,6 @@ private:
 	Style::FontStyle style;
 	Style::FontStyle style;
 	Style::FontWeight weight;
 	Style::FontWeight weight;
 
 
-	// Only filled if we own the memory used by the FreeType face handle.
-	UniquePtr<byte[]> face_memory;
-
 	// Key is font size
 	// Key is font size
 	using HandleMap = UnorderedMap< int, UniquePtr<FontFaceHandleDefault> >;
 	using HandleMap = UnorderedMap< int, UniquePtr<FontFaceHandleDefault> >;
 	HandleMap handles;
 	HandleMap handles;

+ 28 - 11
Source/Core/FontEngineDefault/FontFamily.cpp

@@ -27,46 +27,63 @@
  */
  */
 
 
 #include "FontFamily.h"
 #include "FontFamily.h"
+#include "../../../Include/RmlUi/Core/Math.h"
 #include "FontFace.h"
 #include "FontFace.h"
+#include <limits.h>
 
 
 namespace Rml {
 namespace Rml {
 
 
 FontFamily::FontFamily(const String& name) : name(name)
 FontFamily::FontFamily(const String& name) : name(name)
+{}
+
+FontFamily::~FontFamily()
 {
 {
+	// Multiple face entries may share memory within a single font family, although only one of them owns it. Here we make sure that all the face
+	// destructors are run before all the memory is released. This way we don't leave any hanging references to invalidated memory.
+	for (FontFaceEntry& entry : font_faces)
+		entry.face.reset();
 }
 }
 
 
 // Returns a handle to the most appropriate font in the family, at the correct size.
 // Returns a handle to the most appropriate font in the family, at the correct size.
 FontFaceHandleDefault* FontFamily::GetFaceHandle(Style::FontStyle style, Style::FontWeight weight, int size)
 FontFaceHandleDefault* FontFamily::GetFaceHandle(Style::FontStyle style, Style::FontWeight weight, int size)
 {
 {
-	// Search for a face of the same style, and match the weight as closely as we can.
+	int best_dist = INT_MAX;
 	FontFace* matching_face = nullptr;
 	FontFace* matching_face = nullptr;
 	for (size_t i = 0; i < font_faces.size(); i++)
 	for (size_t i = 0; i < font_faces.size(); i++)
 	{
 	{
-		// If we've found a face matching the style, then ... great! We'll match it regardless of the weight. However,
-		// if it's a perfect match, then we'll stop looking altogether.
-		if (font_faces[i]->GetStyle() == style)
-		{
-			matching_face = font_faces[i].get();
+		FontFace* face = font_faces[i].face.get();
 
 
-			if (font_faces[i]->GetWeight() == weight)
+		if (face->GetStyle() == style)
+		{
+			const int dist = Math::AbsoluteValue((int)face->GetWeight() - (int)weight);
+			if (dist == 0)
+			{
+				// Direct match for weight, break the loop early.
+				matching_face = face;
 				break;
 				break;
+			}
+			else if (dist < best_dist)
+			{
+				// Best match so far for weight, store the face and dist.
+				matching_face = face;
+				best_dist = dist;
+			}
 		}
 		}
 	}
 	}
 
 
-	if (matching_face == nullptr)
+	if (!matching_face)
 		return nullptr;
 		return nullptr;
 
 
 	return matching_face->GetHandle(size, false);
 	return matching_face->GetHandle(size, false);
 }
 }
 
 
-
 // Adds a new face to the family.
 // Adds a new face to the family.
 FontFace* FontFamily::AddFace(FontFaceHandleFreetype ft_face, Style::FontStyle style, Style::FontWeight weight, UniquePtr<byte[]> face_memory)
 FontFace* FontFamily::AddFace(FontFaceHandleFreetype ft_face, Style::FontStyle style, Style::FontWeight weight, UniquePtr<byte[]> face_memory)
 {
 {
-	auto face = MakeUnique<FontFace>(ft_face, style, weight, std::move(face_memory));
+	auto face = MakeUnique<FontFace>(ft_face, style, weight);
 	FontFace* result = face.get();
 	FontFace* result = face.get();
 
 
-	font_faces.push_back(std::move(face));
+	font_faces.push_back(FontFaceEntry{std::move(face), std::move(face_memory)});
 
 
 	return result;
 	return result;
 }
 }

+ 8 - 1
Source/Core/FontEngineDefault/FontFamily.h

@@ -44,6 +44,7 @@ class FontFamily
 {
 {
 public:
 public:
 	FontFamily(const String& name);
 	FontFamily(const String& name);
+	~FontFamily();
 
 
 	/// Returns a handle to the most appropriate font in the family, at the correct size.
 	/// Returns a handle to the most appropriate font in the family, at the correct size.
 	/// @param[in] style The style of the desired handle.
 	/// @param[in] style The style of the desired handle.
@@ -64,7 +65,13 @@ public:
 protected:
 protected:
 	String name;
 	String name;
 
 
-	using FontFaceList = Vector< UniquePtr<FontFace> >;
+	struct FontFaceEntry {
+		UniquePtr<FontFace> face;
+		// Only filled if we own the memory used by the face's FreeType handle. May be shared with other faces in this family.
+		UniquePtr<byte[]> face_memory;
+	};
+
+	using FontFaceList = Vector<FontFaceEntry>;
 	FontFaceList font_faces;
 	FontFaceList font_faces;
 };
 };
 
 

+ 80 - 15
Source/Core/FontEngineDefault/FontProvider.cpp

@@ -34,6 +34,7 @@
 #include "../../../Include/RmlUi/Core/Core.h"
 #include "../../../Include/RmlUi/Core/Core.h"
 #include "../../../Include/RmlUi/Core/FileInterface.h"
 #include "../../../Include/RmlUi/Core/FileInterface.h"
 #include "../../../Include/RmlUi/Core/Log.h"
 #include "../../../Include/RmlUi/Core/Log.h"
+#include "../../../Include/RmlUi/Core/Math.h"
 #include "../../../Include/RmlUi/Core/StringUtilities.h"
 #include "../../../Include/RmlUi/Core/StringUtilities.h"
 #include <algorithm>
 #include <algorithm>
 
 
@@ -103,7 +104,7 @@ FontFaceHandleDefault* FontProvider::GetFallbackFontFace(int index, int font_siz
 }
 }
 
 
 
 
-bool FontProvider::LoadFontFace(const String& file_name, bool fallback_face)
+bool FontProvider::LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight)
 {
 {
 	FileInterface* file_interface = GetFileInterface();
 	FileInterface* file_interface = GetFileInterface();
 	FileHandle handle = file_interface->Open(file_name);
 	FileHandle handle = file_interface->Open(file_name);
@@ -121,13 +122,14 @@ bool FontProvider::LoadFontFace(const String& file_name, bool fallback_face)
 	file_interface->Read(buffer, length, handle);
 	file_interface->Read(buffer, length, handle);
 	file_interface->Close(handle);
 	file_interface->Close(handle);
 
 
-	bool result = Get().LoadFontFace(buffer, (int)length, fallback_face, std::move(buffer_ptr), file_name);
+	bool result = Get().LoadFontFace(buffer, (int)length, fallback_face, std::move(buffer_ptr), file_name, {}, Style::FontStyle::Normal, weight);
 
 
 	return result;
 	return result;
 }
 }
 
 
 
 
-bool FontProvider::LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face)
+bool FontProvider::LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight,
+	bool fallback_face)
 {
 {
 	const String source = "memory";
 	const String source = "memory";
 	
 	
@@ -136,36 +138,99 @@ bool FontProvider::LoadFontFace(const byte* data, int data_size, const String& f
 	return result;
 	return result;
 }
 }
 
 
+
 bool FontProvider::LoadFontFace(const byte* data, int data_size, bool fallback_face, UniquePtr<byte[]> face_memory, const String& source,
 bool FontProvider::LoadFontFace(const byte* data, int data_size, bool fallback_face, UniquePtr<byte[]> face_memory, const String& source,
 	String font_family, Style::FontStyle style, Style::FontWeight weight)
 	String font_family, Style::FontStyle style, Style::FontWeight weight)
 {
 {
-	FontFaceHandleFreetype ft_face = FreeType::LoadFace(data, data_size, source);
-	
-	if (!ft_face)
+	using Style::FontWeight;
+
+	Vector<FaceVariation> face_variations;
+	if (!FreeType::GetFaceVariations(data, data_size, face_variations))
 	{
 	{
-		Log::Message(Log::LT_ERROR, "Failed to load font face %s from '%s'.", font_family.c_str(), source.c_str());
+		Log::Message(Log::LT_ERROR, "Failed to load font face from '%s': Invalid or unsupported font face file format.", source.c_str());
 		return false;
 		return false;
 	}
 	}
 
 
-	if (font_family.empty())
+	Vector<FaceVariation> load_variations;
+	if (face_variations.empty())
 	{
 	{
-		FreeType::GetFaceStyle(ft_face, font_family, style, weight);
+		load_variations.push_back(FaceVariation{Style::FontWeight::Auto, 0, 0});
+	}
+	else
+	{
+		// Iterate through all the face variations and pick the ones to load. The list is already sorted by (weight, width). When weight is set to
+		// 'auto' we load all the weights of the face. However, we only want to load one width for each weight.
+		for (auto it = face_variations.begin(); it != face_variations.end();)
+		{
+			if (weight != FontWeight::Auto && it->weight != weight)
+			{
+				++it;
+				continue;
+			}
+
+			// We don't currently have any way for users to select widths, so we search for a regular (medium) value here.
+			constexpr int search_width = 100;
+			const FontWeight current_weight = it->weight;
+
+			int best_width_distance = Math::AbsoluteValue((int)it->width - search_width);
+			auto it_best_width = it;
+			
+			// Search forward to find the best 'width' with the same weight.
+			for (++it; it != face_variations.end(); ++it)
+			{
+				if (it->weight != current_weight)
+					break;
+
+				const int width_distance = Math::AbsoluteValue((int)it->width - search_width);
+				if (width_distance < best_width_distance)
+				{
+					best_width_distance = width_distance;
+					it_best_width = it;
+				}
+			}
+
+			load_variations.push_back(*it_best_width);
+		}
 	}
 	}
 
 
-	const String font_face_description = FontFaceDescription(font_family, style, weight);
-
-	if (!AddFace(ft_face, font_family, style, weight, fallback_face, std::move(face_memory)))
+	if (load_variations.empty())
 	{
 	{
-		Log::Message(Log::LT_ERROR, "Failed to load font face %s from '%s'.", font_face_description.c_str(), source.c_str());
+		Log::Message(Log::LT_ERROR, "Failed to load font face from '%s': Could not locate face with weight %d.", source.c_str(), (int)weight);
 		return false;
 		return false;
 	}
 	}
 
 
-	Log::Message(Log::LT_INFO, "Loaded font face %s from '%s'.", font_face_description.c_str(), source.c_str());
+	for (const FaceVariation& variation : load_variations)
+	{
+		FontFaceHandleFreetype ft_face = FreeType::LoadFace(data, data_size, source, variation.named_instance_index);
+		if (!ft_face)
+			return false;
+
+		if (font_family.empty())
+			FreeType::GetFaceStyle(ft_face, &font_family, &style, nullptr);
+		if (weight == FontWeight::Auto)
+			FreeType::GetFaceStyle(ft_face, nullptr, nullptr, &weight);
+
+		const FontWeight variation_weight = (variation.weight == FontWeight::Auto ? weight : variation.weight);
+		const String font_face_description = FontFaceDescription(font_family, style, variation_weight);
+
+		if (!AddFace(ft_face, font_family, style, variation_weight, fallback_face, std::move(face_memory)))
+		{
+			Log::Message(Log::LT_ERROR, "Failed to load font face %s from '%s'.", font_face_description.c_str(), source.c_str());
+			return false;
+		}
+
+		Log::Message(Log::LT_INFO, "Loaded font face %s from '%s'.", font_face_description.c_str(), source.c_str());
+	}
+
 	return true;
 	return true;
 }
 }
 
 
-bool FontProvider::AddFace(FontFaceHandleFreetype face, const String& family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face, UniquePtr<byte[]> face_memory)
+bool FontProvider::AddFace(FontFaceHandleFreetype face, const String& family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face,
+	UniquePtr<byte[]> face_memory)
 {
 {
+	if (family.empty() || weight == Style::FontWeight::Auto)
+		return false;
+
 	String family_lower = StringUtilities::ToLower(family);
 	String family_lower = StringUtilities::ToLower(family);
 	FontFamily* font_family = nullptr;
 	FontFamily* font_family = nullptr;
 	auto it = font_families.find(family_lower);
 	auto it = font_families.find(family_lower);

+ 4 - 3
Source/Core/FontEngineDefault/FontProvider.h

@@ -61,7 +61,7 @@ public:
 	static FontFaceHandleDefault* GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size);
 	static FontFaceHandleDefault* 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.
 	/// 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, bool fallback_face);
+	static bool LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight = Style::FontWeight::Auto);
 
 
 	/// Adds a new font face from memory.
 	/// Adds a new font face from memory.
 	static bool LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face);
 	static bool LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face);
@@ -79,9 +79,10 @@ private:
 	static FontProvider& Get();
 	static FontProvider& Get();
 
 
 	bool LoadFontFace(const byte* data, int data_size, bool fallback_face, UniquePtr<byte[]> face_memory, const String& source,
 	bool LoadFontFace(const byte* data, int data_size, bool fallback_face, UniquePtr<byte[]> face_memory, const String& source,
-		String font_family = {}, Style::FontStyle style = Style::FontStyle::Normal, Style::FontWeight weight = Style::FontWeight::Normal);
+		String font_family, Style::FontStyle style, Style::FontWeight weight);
 
 
-	bool AddFace(FontFaceHandleFreetype face, const String& family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face, 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 FontFaceList = Vector<FontFace*>;
 	using FontFamilyMap = UnorderedMap< String, UniquePtr<FontFamily>>;
 	using FontFamilyMap = UnorderedMap< String, UniquePtr<FontFamily>>;

+ 16 - 4
Source/Core/FontEngineDefault/FontTypes.h

@@ -15,7 +15,7 @@
  *
  *
  * The above copyright notice and this permission notice shall be included in
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  * all copies or substantial portions of the Software.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -29,16 +29,15 @@
 #ifndef RMLUI_CORE_FONTENGINEDEFAULT_FONTTYPES_H
 #ifndef RMLUI_CORE_FONTENGINEDEFAULT_FONTTYPES_H
 #define RMLUI_CORE_FONTENGINEDEFAULT_FONTTYPES_H
 #define RMLUI_CORE_FONTENGINEDEFAULT_FONTTYPES_H
 
 
-#include "../../../Include/RmlUi/Core/Types.h"
 #include "../../../Include/RmlUi/Core/ComputedValues.h"
 #include "../../../Include/RmlUi/Core/ComputedValues.h"
 #include "../../../Include/RmlUi/Core/FontGlyph.h"
 #include "../../../Include/RmlUi/Core/FontGlyph.h"
+#include "../../../Include/RmlUi/Core/Types.h"
 
 
 namespace Rml {
 namespace Rml {
 
 
 using FontFaceHandleFreetype = uintptr_t;
 using FontFaceHandleFreetype = uintptr_t;
 
 
-struct FontMetrics 
-{
+struct FontMetrics {
 	int size;
 	int size;
 	int x_height;
 	int x_height;
 	int line_height;
 	int line_height;
@@ -48,5 +47,18 @@ struct FontMetrics
 	float underline_thickness;
 	float underline_thickness;
 };
 };
 
 
+struct FaceVariation {
+	Style::FontWeight weight;
+	uint16_t width;
+	int named_instance_index;
+};
+
+inline bool operator<(const FaceVariation& a, const FaceVariation& b)
+{
+	if (a.weight == b.weight)
+		return a.width < b.width;
+	return a.weight < b.weight;
+}
+
 } // namespace Rml
 } // namespace Rml
 #endif
 #endif

+ 83 - 9
Source/Core/FontEngineDefault/FreeTypeInterface.cpp

@@ -28,11 +28,14 @@
 
 
 #include "FreeTypeInterface.h"
 #include "FreeTypeInterface.h"
 #include "../../../Include/RmlUi/Core/Log.h"
 #include "../../../Include/RmlUi/Core/Log.h"
-
+#include <algorithm>
 #include <string.h>
 #include <string.h>
 #include <limits.h>
 #include <limits.h>
+
 #include <ft2build.h>
 #include <ft2build.h>
 #include FT_FREETYPE_H
 #include FT_FREETYPE_H
+#include FT_MULTIPLE_MASTERS_H
+#include FT_TRUETYPE_TABLES_H
 
 
 namespace Rml {
 namespace Rml {
 
 
@@ -45,6 +48,11 @@ static bool SetFontSize(FT_Face ft_face, int font_size, float& out_bitmap_scalin
 static void BitmapDownscale(byte* bitmap_new, int new_width, int new_height, const byte* bitmap_source, int width, int height, int pitch,
 static void BitmapDownscale(byte* bitmap_new, int new_width, int new_height, const byte* bitmap_source, int width, int height, int pitch,
 	ColorFormat color_format);
 	ColorFormat color_format);
 
 
+static int ConvertFixed16_16ToInt(int32_t fx)
+{
+	return fx / 0x10000;
+}
+
 bool FreeType::Initialise()
 bool FreeType::Initialise()
 {
 {
 	RMLUI_ASSERT(!ft_library);
 	RMLUI_ASSERT(!ft_library);
@@ -69,14 +77,70 @@ void FreeType::Shutdown()
 	}
 	}
 }
 }
 
 
-// Loads a FreeType face from memory.
-FontFaceHandleFreetype FreeType::LoadFace(const byte* data, int data_length, const String& source)
+bool FreeType::GetFaceVariations(const byte* data, int data_length, Vector<FaceVariation>& out_face_variations)
 {
 {
 	RMLUI_ASSERT(ft_library);
 	RMLUI_ASSERT(ft_library);
 
 
 	FT_Face face = nullptr;
 	FT_Face face = nullptr;
-	int error = FT_New_Memory_Face(ft_library, (const FT_Byte*)data, data_length, 0, &face);
-	if (error != 0)
+	FT_Error error = FT_New_Memory_Face(ft_library, (const FT_Byte*)data, data_length, 0, &face);
+	if (error)
+		return false;
+
+	FT_MM_Var* var = nullptr;
+	error = FT_Get_MM_Var(face, &var);
+	if (error)
+		return true;
+
+	unsigned int axis_index_weight = 0;
+	unsigned int axis_index_width = 1;
+
+	const unsigned int num_axis = var->num_axis;
+	for (unsigned int i = 0; i < num_axis; i++)
+	{
+		switch (var->axis[i].tag)
+		{
+		case 0x77676874: // 'wght'
+			axis_index_weight = i;
+			break;
+		case 0x77647468: // 'wdth'
+			axis_index_width = i;
+			break;
+		}
+	}
+
+	if (num_axis > 0)
+	{
+		for (unsigned int i = 0; i < var->num_namedstyles; i++)
+		{
+			uint16_t weight = (axis_index_weight < num_axis ? (uint16_t)ConvertFixed16_16ToInt(var->namedstyle[i].coords[axis_index_weight]) : 0);
+			uint16_t width = (axis_index_width < num_axis ? (uint16_t)ConvertFixed16_16ToInt(var->namedstyle[i].coords[axis_index_width]) : 0);
+			int named_instance_index = (i + 1);
+
+			out_face_variations.push_back(
+				FaceVariation{
+					weight == 0 ? Style::FontWeight::Normal : (Style::FontWeight)weight,
+					width == 0 ? (uint16_t)100 : width,
+					named_instance_index
+				}
+			);
+		}
+	}
+
+	std::sort(out_face_variations.begin(), out_face_variations.end());
+
+	FT_Done_MM_Var(ft_library, var);
+	FT_Done_Face(face);
+
+	return true;
+}
+
+FontFaceHandleFreetype FreeType::LoadFace(const byte* data, int data_length, const String& source, int named_style_index)
+{
+	RMLUI_ASSERT(ft_library);
+
+	FT_Face face = nullptr;
+	FT_Error error = FT_New_Memory_Face(ft_library, (const FT_Byte*)data, data_length, (named_style_index << 16), &face);
+	if (error)
 	{
 	{
 		Log::Message(Log::LT_ERROR, "FreeType error %d while loading face from %s.", error, source.c_str());
 		Log::Message(Log::LT_ERROR, "FreeType error %d while loading face from %s.", error, source.c_str());
 		return 0;
 		return 0;
@@ -105,13 +169,23 @@ bool FreeType::ReleaseFace(FontFaceHandleFreetype in_face)
 	return (error == 0);
 	return (error == 0);
 }
 }
 
 
-void FreeType::GetFaceStyle(FontFaceHandleFreetype in_face, String& font_family, Style::FontStyle& style, Style::FontWeight& weight)
+void FreeType::GetFaceStyle(FontFaceHandleFreetype in_face, String* font_family, Style::FontStyle* style, Style::FontWeight* weight)
 {
 {
 	FT_Face face = (FT_Face)in_face;
 	FT_Face face = (FT_Face)in_face;
 
 
-	font_family = face->family_name;
-	style = face->style_flags & FT_STYLE_FLAG_ITALIC ? Style::FontStyle::Italic : Style::FontStyle::Normal;
-	weight = face->style_flags & FT_STYLE_FLAG_BOLD ? Style::FontWeight::Bold : Style::FontWeight::Normal;
+	if (font_family)
+		*font_family = face->family_name;
+	if (style)
+		*style = face->style_flags & FT_STYLE_FLAG_ITALIC ? Style::FontStyle::Italic : Style::FontStyle::Normal;
+
+	if (weight)
+	{
+		TT_OS2* font_table = (TT_OS2*)FT_Get_Sfnt_Table(face, FT_SFNT_OS2);
+		if (font_table && font_table->usWeightClass != 0)
+			*weight = (Style::FontWeight)font_table->usWeightClass;
+		else
+			*weight = (face->style_flags & FT_STYLE_FLAG_BOLD) == FT_STYLE_FLAG_BOLD ? Style::FontWeight::Bold : Style::FontWeight::Normal;
+	}
 }
 }
 
 
 // Initialises the handle so it is able to render text.
 // Initialises the handle so it is able to render text.

+ 6 - 3
Source/Core/FontEngineDefault/FreeTypeInterface.h

@@ -40,14 +40,17 @@ bool Initialise();
 // Shutdown FreeType library.
 // Shutdown FreeType library.
 void Shutdown();
 void Shutdown();
 
 
+// Returns a sorted list of available font variations for the font face located in memory.
+bool GetFaceVariations(const byte* data, int data_length, Vector<FaceVariation>& out_face_variations);
+
 // Loads a FreeType face from memory, 'source' is only used for logging.
 // Loads a FreeType face from memory, 'source' is only used for logging.
-FontFaceHandleFreetype LoadFace(const byte* data, int data_length, const String& source);
+FontFaceHandleFreetype LoadFace(const byte* data, int data_length, const String& source, int named_instance_index = 0);
 
 
 // Releases the FreeType face.
 // Releases the FreeType face.
 bool ReleaseFace(FontFaceHandleFreetype face);
 bool ReleaseFace(FontFaceHandleFreetype face);
 
 
-// Retrieves the font family, style and weight of the given font face.
-void GetFaceStyle(FontFaceHandleFreetype face, String& font_family, Style::FontStyle& style, Style::FontWeight& weight);
+// Retrieves the font family, style and weight of the given font face. Use nullptr to ignore a property.
+void GetFaceStyle(FontFaceHandleFreetype face, String* font_family, Style::FontStyle* style, Style::FontWeight* weight);
 
 
 // Initializes a face for a given font size. Glyphs are filled with the ASCII subset, and the font face metrics are set.
 // Initializes a face for a given font size. Glyphs are filled with the ASCII subset, and the font face metrics are set.
 bool InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics, bool load_default_glyphs);
 bool InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics, bool load_default_glyphs);

+ 1 - 1
Source/Core/FontEngineInterface.cpp

@@ -34,7 +34,7 @@ FontEngineInterface::FontEngineInterface() {}
 
 
 FontEngineInterface::~FontEngineInterface() {}
 FontEngineInterface::~FontEngineInterface() {}
 
 
-bool FontEngineInterface::LoadFontFace(const String& /*file_name*/, bool /*fallback_face*/)
+bool FontEngineInterface::LoadFontFace(const String& /*file_name*/, bool /*fallback_face*/, Style::FontWeight /*weight*/)
 {
 {
 	return false;
 	return false;
 }
 }

+ 2 - 0
Source/Core/LayoutInlineBoxText.cpp

@@ -48,6 +48,8 @@ String FontFaceDescription(const String& font_family, Style::FontStyle style, St
 		font_attributes += "italic, ";
 		font_attributes += "italic, ";
 	if (weight == Style::FontWeight::Bold)
 	if (weight == Style::FontWeight::Bold)
 		font_attributes += "bold, ";
 		font_attributes += "bold, ";
+	else if (weight != Style::FontWeight::Auto && weight != Style::FontWeight::Normal)
+		font_attributes += "weight=" + ToString((int)weight) + ", ";
 
 
 	if (font_attributes.empty())
 	if (font_attributes.empty())
 		font_attributes = "regular";
 		font_attributes = "regular";

+ 1 - 1
Source/Core/StyleSheetSpecification.cpp

@@ -379,7 +379,7 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 
 
 	RegisterProperty(PropertyId::FontFamily, "font-family", "", true, true).AddParser("string");
 	RegisterProperty(PropertyId::FontFamily, "font-family", "", true, true).AddParser("string");
 	RegisterProperty(PropertyId::FontStyle, "font-style", "normal", true, true).AddParser("keyword", "normal, italic");
 	RegisterProperty(PropertyId::FontStyle, "font-style", "normal", true, true).AddParser("keyword", "normal, italic");
-	RegisterProperty(PropertyId::FontWeight, "font-weight", "normal", true, true).AddParser("keyword", "normal, bold");
+	RegisterProperty(PropertyId::FontWeight, "font-weight", "normal", true, true).AddParser("keyword", "normal=400, bold=700").AddParser("number");
 	RegisterProperty(PropertyId::FontSize, "font-size", "12px", true, true).AddParser("length").AddParser("length_percent").SetRelativeTarget(RelativeTarget::ParentFontSize);
 	RegisterProperty(PropertyId::FontSize, "font-size", "12px", true, true).AddParser("length").AddParser("length_percent").SetRelativeTarget(RelativeTarget::ParentFontSize);
 	RegisterShorthand(ShorthandId::Font, "font", "font-style, font-weight, font-size, font-family", ShorthandType::FallThrough);
 	RegisterShorthand(ShorthandId::Font, "font", "font-style, font-weight, font-size, font-family", ShorthandType::FallThrough);