瀏覽代碼

Add font-kerning property (#843)

Hannaford Schäfer 2 月之前
父節點
當前提交
56231a6c87

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

@@ -113,7 +113,7 @@ namespace Style {
 
 	struct InheritedValues {
 		InheritedValues() :
-			font_weight(FontWeight::Normal), has_letter_spacing(0), font_style(FontStyle::Normal), has_font_effect(false),
+			font_weight(FontWeight::Normal), font_kerning(FontKerning::Auto), has_letter_spacing(0), font_style(FontStyle::Normal), has_font_effect(false),
 			pointer_events(PointerEvents::Auto), focus(Focus::Auto), text_align(TextAlign::Left), text_decoration(TextDecoration::None),
 			text_transform(TextTransform::None), white_space(WhiteSpace::Normal), word_break(WordBreak::Normal), direction(Direction::Auto),
 			line_height_inherit_type(LineHeight::Number)
@@ -129,6 +129,7 @@ namespace Style {
 		Colourb color = Colourb(255, 255, 255);
 
 		FontWeight font_weight : 10;
+		FontKerning font_kerning : 2;
 		uint16_t has_letter_spacing : 1;
 
 		FontStyle font_style : 1;
@@ -259,6 +260,7 @@ namespace Style {
 		bool           has_font_effect()  const { return inherited.has_font_effect; }
 		FontStyle      font_style()       const { return inherited.font_style; }
 		FontWeight     font_weight()      const { return inherited.font_weight; }
+		FontKerning    font_kerning()     const { return inherited.font_kerning; }
 		PointerEvents  pointer_events()   const { return inherited.pointer_events; }
 		Focus          focus()            const { return inherited.focus; }
 		TextAlign      text_align()       const { return inherited.text_align; }
@@ -359,6 +361,7 @@ namespace Style {
 		void has_font_effect   (bool value)           { inherited.has_font_effect    = value; }
 		void font_style        (FontStyle value)      { inherited.font_style         = value; }
 		void font_weight       (FontWeight value)     { inherited.font_weight        = value; }
+		void font_kerning      (FontKerning value)    { inherited.font_kerning       = value; }
 		void pointer_events    (PointerEvents value)  { inherited.pointer_events     = value; }
 		void focus             (Focus value)          { inherited.focus              = value; }
 		void text_align        (TextAlign value)      { inherited.text_align         = value; }

+ 1 - 0
Include/RmlUi/Core/ID.h

@@ -126,6 +126,7 @@ enum class PropertyId : uint8_t {
 	FontStyle,
 	FontWeight,
 	FontSize,
+	FontKerning,
 	LetterSpacing,
 	TextAlign,
 	TextDecoration,

+ 1 - 0
Include/RmlUi/Core/StyleTypes.h

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

+ 1 - 0
Include/RmlUi/Core/TextShapingContext.h

@@ -40,6 +40,7 @@ namespace Rml {
 struct TextShapingContext {
 	const String& language;
 	Style::Direction text_direction = Style::Direction::Auto;
+	Style::FontKerning font_kerning = Style::FontKerning::Auto;
 	float letter_spacing = 0.0f; // Measured in pixels.
 };
 

+ 41 - 8
Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.cpp

@@ -44,6 +44,32 @@ static bool IsControlCharacter(Character c)
 	return (char32_t)c < U' ' || ((char32_t)c >= U'\x7F' && (char32_t)c <= U'\x9F');
 }
 
+static bool IsKerningEnabled(const TextShapingContext& text_shaping_context, int font_size)
+{
+	static constexpr int minimum_font_size_to_enable_kerning = 14;
+
+	switch (text_shaping_context.font_kerning)
+	{
+	case Style::FontKerning::Normal: return true;
+	case Style::FontKerning::None: return false;
+	default: return font_size >= minimum_font_size_to_enable_kerning;
+	}
+}
+
+static Vector<hb_feature_t> GetTextShapingFeatures(const TextShapingContext& text_shaping_context, int font_size)
+{
+	Vector<hb_feature_t> shaping_features;
+
+	if (!IsKerningEnabled(text_shaping_context, font_size))
+	{
+		// Kerning is enabled by default in HarfBuzz, so we need to explicitly disable it.
+		shaping_features.emplace_back();
+		hb_feature_from_string("kern=0", -1, &shaping_features.back());
+	}
+
+	return shaping_features;
+}
+
 FontFaceHandleHarfBuzz::FontFaceHandleHarfBuzz()
 {
 	base_layer = nullptr;
@@ -111,7 +137,10 @@ int FontFaceHandleHarfBuzz::GetStringWidth(StringView string, const TextShapingC
 	RMLUI_ASSERT(shaping_buffer != nullptr);
 	ConfigureTextShapingBuffer(shaping_buffer, string, text_shaping_context, registered_languages, nullptr);
 	hb_buffer_add_utf8(shaping_buffer, string.begin(), (int)string.size(), 0, (int)string.size());
-	hb_shape(hb_font, shaping_buffer, nullptr, 0);
+
+	Vector<hb_feature_t> shaping_features = GetTextShapingFeatures(text_shaping_context, metrics.size);
+	const hb_feature_t* shaping_features_pointer = !shaping_features.empty() ? shaping_features.data() : nullptr;
+	hb_shape(hb_font, shaping_buffer, shaping_features_pointer, (unsigned int)shaping_features.size());
 
 	unsigned int glyph_count = 0;
 	hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(shaping_buffer, &glyph_count);
@@ -140,7 +169,7 @@ int FontFaceHandleHarfBuzz::GetStringWidth(StringView string, const TextShapingC
 			{
 				// Unsupported cluster detected; use fallback cluster glyph if one is available.
 				const Vector<FontClusterGlyphData>* cluster_glyphs =
-					GetOrAppendFallbackClusterGlyphs(cluster_string, text_shaping_context, registered_languages);
+					GetOrAppendFallbackClusterGlyphs(cluster_string, text_shaping_context, registered_languages, shaping_features);
 
 				if (cluster_glyphs)
 				{
@@ -289,6 +318,8 @@ int FontFaceHandleHarfBuzz::GenerateString(RenderManager& render_manager, Textur
 
 	hb_buffer_t* shaping_buffer = hb_buffer_create();
 	RMLUI_ASSERT(shaping_buffer != nullptr);
+	Vector<hb_feature_t> shaping_features = GetTextShapingFeatures(text_shaping_context, metrics.size);
+	const hb_feature_t* shaping_features_pointer = !shaping_features.empty() ? shaping_features.data() : nullptr;
 
 	for (size_t layer_index = 0; layer_index < layer_configuration.size(); ++layer_index)
 	{
@@ -316,7 +347,7 @@ int FontFaceHandleHarfBuzz::GenerateString(RenderManager& render_manager, Textur
 		hb_buffer_clear_contents(shaping_buffer);
 		ConfigureTextShapingBuffer(shaping_buffer, string, text_shaping_context, registered_languages, nullptr);
 		hb_buffer_add_utf8(shaping_buffer, string.begin(), (int)string.size(), 0, (int)string.size());
-		hb_shape(hb_font, shaping_buffer, nullptr, 0);
+		hb_shape(hb_font, shaping_buffer, shaping_features_pointer, (unsigned int)shaping_features.size());
 
 		unsigned int glyph_count = 0;
 		hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(shaping_buffer, &glyph_count);
@@ -348,7 +379,8 @@ int FontFaceHandleHarfBuzz::GenerateString(RenderManager& render_manager, Textur
 				if (cluster_codepoint_count > 1)
 				{
 					// Unsupported cluster detected; use fallback cluster glyph if one is available.
-					const Vector<FontClusterGlyphData>* cluster_glyphs = GetOrAppendFallbackClusterGlyphs(cluster_string, text_shaping_context, registered_languages);
+					const Vector<FontClusterGlyphData>* cluster_glyphs =
+						GetOrAppendFallbackClusterGlyphs(cluster_string, text_shaping_context, registered_languages, shaping_features);
 					
 					if (cluster_glyphs)
 					{
@@ -535,8 +567,9 @@ const FontGlyph* FontFaceHandleHarfBuzz::GetOrAppendFallbackGlyph(Character& cha
 }
 
 bool FontFaceHandleHarfBuzz::AppendFallbackClusterGlyphs(StringView cluster, const TextShapingContext& text_shaping_context,
-	const LanguageDataMap& registered_languages)
+	const LanguageDataMap& registered_languages, Span<struct hb_feature_t> text_shaping_features)
 {
+	const hb_feature_t* shaping_features_pointer = !text_shaping_features.empty() ? text_shaping_features.data() : nullptr;
 	hb_buffer_t* shaping_buffer = hb_buffer_create();
 	RMLUI_ASSERT(shaping_buffer != nullptr);
 
@@ -554,7 +587,7 @@ bool FontFaceHandleHarfBuzz::AppendFallbackClusterGlyphs(StringView cluster, con
 		hb_buffer_clear_contents(shaping_buffer);
 		ConfigureTextShapingBuffer(shaping_buffer, cluster, text_shaping_context, registered_languages, &text_direction);
 		hb_buffer_add_utf8(shaping_buffer, cluster.begin(), (int)cluster.size(), 0, (int)cluster.size());
-		hb_shape(fallback_face->hb_font, shaping_buffer, nullptr, 0);
+		hb_shape(fallback_face->hb_font, shaping_buffer, shaping_features_pointer, (unsigned int)text_shaping_features.size());
 
 		unsigned int glyph_count = 0;
 		hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(shaping_buffer, &glyph_count);
@@ -615,7 +648,7 @@ bool FontFaceHandleHarfBuzz::AppendFallbackClusterGlyphs(StringView cluster, con
 }
 
 const Vector<FontClusterGlyphData>* FontFaceHandleHarfBuzz::GetOrAppendFallbackClusterGlyphs(StringView cluster,
-	const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages)
+	const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages, Span<struct hb_feature_t> text_shaping_features)
 {
 	String cluster_string(cluster);
 	auto fallback_cluster_glyphs_location = fallback_cluster_glyphs.find(cluster_string);
@@ -624,7 +657,7 @@ const Vector<FontClusterGlyphData>* FontFaceHandleHarfBuzz::GetOrAppendFallbackC
 		return &fallback_cluster_glyphs_location->second;
 	}
 
-	bool result = AppendFallbackClusterGlyphs(cluster, text_shaping_context, registered_languages);
+	bool result = AppendFallbackClusterGlyphs(cluster, text_shaping_context, registered_languages, text_shaping_features);
 
 	if (result)
 	{

+ 5 - 3
Samples/basic/harfbuzz/src/FontFaceHandleHarfBuzz.h

@@ -48,6 +48,7 @@ using Rml::Queue;
 using Rml::RenderManager;
 using Rml::SharedPtr;
 using Rml::SmallUnorderedMap;
+using Rml::Span;
 using Rml::String;
 using Rml::StringView;
 using Rml::TextShapingContext;
@@ -109,7 +110,7 @@ public:
 	/// @param[in] registered_languages A list of languages registered in the font engine interface.
 	/// @param[in] layer_configuration Face configuration index to use for generating string.
 	/// @return The width, in pixels, of the string geometry.
-	int GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, StringView, Vector2f position, ColourbPremultiplied colour,
+	int GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, StringView string, Vector2f position, ColourbPremultiplied colour,
 		float opacity, const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages, int layer_configuration = 0);
 
 	/// Version is changed whenever the layers are dirtied, requiring regeneration of string geometry.
@@ -136,15 +137,16 @@ private:
 
 	// Build and append fallback cluster glyph to 'fallback_cluster_glyphs'.
 	bool AppendFallbackClusterGlyphs(StringView cluster, const TextShapingContext& text_shaping_context,
-		const LanguageDataMap& registered_languages);
+		const LanguageDataMap& registered_languages, Span<struct hb_feature_t> text_shaping_features);
 
 	/// Retrieve a fallback cluster glyph from the given cluster and text-shaping/language data, building and appending a new fallback cluster glyph if not already built.
 	/// @param[in] cluster  The cluster.
 	/// @param[in] text_shaping_context  Extra parameters that provide context for text shaping.
 	/// @param[in] registered_languages  A list of languages registered in the font engine interface.
+	/// @param[in] text_shaping_features  A list of OpenType feature settings used for text shaping.
 	/// @return The fallback glyphs of the cluster.
 	const Vector<FontClusterGlyphData>* GetOrAppendFallbackClusterGlyphs(StringView cluster, const TextShapingContext& text_shaping_context,
-		const LanguageDataMap& registered_languages);
+		const LanguageDataMap& registered_languages, Span<struct hb_feature_t> text_shaping_features);
 
 	// Regenerate layers if dirty, such as after adding new glyphs.
 	bool UpdateLayersOnDirty();

+ 1 - 1
Source/Core/DecoratorText.cpp

@@ -92,7 +92,7 @@ bool DecoratorText::GenerateGeometry(Element* element, ElementData& element_data
 		return true;
 
 	const auto& computed = element->GetComputedValues();
-	const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.letter_spacing()};
+	const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.font_kerning(), computed.letter_spacing()};
 
 	const int string_width = font_engine_interface->GetStringWidth(font_face_handle, text, text_shaping_context);
 

+ 1 - 1
Source/Core/Element.cpp

@@ -1920,7 +1920,7 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
 
 	const bool font_changed = (changed_properties.Contains(PropertyId::FontFamily) || changed_properties.Contains(PropertyId::FontStyle) ||
 		changed_properties.Contains(PropertyId::FontWeight) || changed_properties.Contains(PropertyId::FontSize) ||
-		changed_properties.Contains(PropertyId::LetterSpacing));
+		changed_properties.Contains(PropertyId::FontKerning) || changed_properties.Contains(PropertyId::LetterSpacing));
 
 	// Dirty the effects data when their visual looks may have changed.
 	if (border_radius_changed ||                            //

+ 4 - 0
Source/Core/ElementStyle.cpp

@@ -774,6 +774,10 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			// (font-size computed above)
 			dirty_font_face_handle = true;
 			break;
+		case PropertyId::FontKerning:
+			values.font_kerning((FontKerning)p->Get<int>());
+			dirty_font_face_handle = true;
+			break;
 		case PropertyId::LetterSpacing:
 			values.has_letter_spacing(p->unit != Unit::KEYWORD);
 			dirty_font_face_handle = true;

+ 3 - 2
Source/Core/ElementText.cpp

@@ -212,7 +212,7 @@ bool ElementText::GenerateLine(String& line, int& line_length, float& line_width
 	bool break_at_endline =
 		white_space_property == WhiteSpace::Pre || white_space_property == WhiteSpace::Prewrap || white_space_property == WhiteSpace::Preline;
 
-	const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.letter_spacing()};
+	const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.font_kerning(), computed.letter_spacing()};
 	TextTransform text_transform_property = computed.text_transform();
 	WordBreak word_break = computed.word_break();
 
@@ -362,6 +362,7 @@ void ElementText::OnPropertyChange(const PropertyIdSet& changed_properties)
 		changed_properties.Contains(PropertyId::FontWeight) ||     //
 		changed_properties.Contains(PropertyId::FontStyle) ||      //
 		changed_properties.Contains(PropertyId::FontSize) ||       //
+		changed_properties.Contains(PropertyId::FontKerning) ||    //
 		changed_properties.Contains(PropertyId::LetterSpacing) ||  //
 		changed_properties.Contains(PropertyId::RmlUi_Language) || //
 		changed_properties.Contains(PropertyId::RmlUi_Direction))
@@ -452,7 +453,7 @@ void ElementText::GenerateGeometry(RenderManager& render_manager, const FontFace
 	RMLUI_ZoneScopedC(0xD2691E);
 
 	const auto& computed = GetComputedValues();
-	const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.letter_spacing()};
+	const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.font_kerning(), computed.letter_spacing()};
 
 	TexturedMeshList mesh_list;
 	mesh_list.reserve(geometry.size());

+ 1 - 1
Source/Core/ElementUtilities.cpp

@@ -133,7 +133,7 @@ float ElementUtilities::GetDensityIndependentPixelRatio(Element* element)
 int ElementUtilities::GetStringWidth(Element* element, StringView string, Character prior_character)
 {
 	const auto& computed = element->GetComputedValues();
-	const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.letter_spacing()};
+	const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.font_kerning(), computed.letter_spacing()};
 
 	FontFaceHandle font_face_handle = element->GetFontFaceHandle();
 	if (font_face_handle == 0)

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

@@ -76,7 +76,7 @@ int FontEngineInterfaceDefault::GetStringWidth(FontFaceHandle handle, StringView
 	Character prior_character)
 {
 	auto handle_default = reinterpret_cast<FontFaceHandleDefault*>(handle);
-	return handle_default->GetStringWidth(string, text_shaping_context.letter_spacing, prior_character);
+	return handle_default->GetStringWidth(string, text_shaping_context, prior_character);
 }
 
 int FontEngineInterfaceDefault::GenerateString(RenderManager& render_manager, FontFaceHandle handle, FontEffectsHandle font_effects_handle,
@@ -84,7 +84,7 @@ int FontEngineInterfaceDefault::GenerateString(RenderManager& render_manager, Fo
 	TexturedMeshList& mesh_list)
 {
 	auto handle_default = reinterpret_cast<FontFaceHandleDefault*>(handle);
-	return handle_default->GenerateString(render_manager, mesh_list, string, position, colour, opacity, text_shaping_context.letter_spacing,
+	return handle_default->GenerateString(render_manager, mesh_list, string, position, colour, opacity, text_shaping_context,
 		(int)font_effects_handle);
 }
 

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

@@ -29,6 +29,7 @@
 #include "FontFaceHandleDefault.h"
 #include "../../../Include/RmlUi/Core/Profiling.h"
 #include "../../../Include/RmlUi/Core/StringUtilities.h"
+#include "../../../Include/RmlUi/Core/StyleTypes.h"
 #include "../TextureLayout.h"
 #include "FontFaceLayer.h"
 #include "FontProvider.h"
@@ -83,11 +84,12 @@ const FontGlyphMap& FontFaceHandleDefault::GetGlyphs() const
 	return glyphs;
 }
 
-int FontFaceHandleDefault::GetStringWidth(StringView string, float letter_spacing, Character prior_character)
+int FontFaceHandleDefault::GetStringWidth(StringView string, const TextShapingContext& text_shaping_context, Character prior_character)
 {
 	RMLUI_ZoneScoped;
 
 	bool has_set_size = false;
+	bool is_kerning_enabled = IsKerningEnabled(text_shaping_context);
 	int width = 0;
 	for (auto it_string = StringIteratorU8(string); it_string; ++it_string)
 	{
@@ -98,11 +100,12 @@ int FontFaceHandleDefault::GetStringWidth(StringView string, float letter_spacin
 			continue;
 
 		// Adjust the cursor for the kerning between this character and the previous one.
-		width += GetKerning(prior_character, character, has_set_size);
+		if (is_kerning_enabled)
+			width += GetKerning(prior_character, character, has_set_size);
 
 		// Adjust the cursor for this character's advance.
 		width += glyph->advance;
-		width += (int)letter_spacing;
+		width += (int)text_shaping_context.letter_spacing;
 
 		prior_character = character;
 	}
@@ -194,7 +197,7 @@ bool FontFaceHandleDefault::GenerateLayerTexture(Vector<byte>& texture_data, Vec
 }
 
 int FontFaceHandleDefault::GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, StringView string, const Vector2f position,
-	const ColourbPremultiplied colour, const float opacity, const float letter_spacing, const int layer_configuration_index)
+	const ColourbPremultiplied colour, const float opacity, const TextShapingContext& text_shaping_context, const int layer_configuration_index)
 {
 	RMLUI_ASSERT(layer_configuration_index >= 0);
 	RMLUI_ASSERT(layer_configuration_index < (int)layer_configurations.size());
@@ -202,6 +205,7 @@ int FontFaceHandleDefault::GenerateString(RenderManager& render_manager, Texture
 	int geometry_index = 0;
 	int line_width = 0;
 	bool has_set_size = false;
+	bool is_kerning_enabled = IsKerningEnabled(text_shaping_context);
 
 	UpdateLayersOnDirty();
 
@@ -249,7 +253,8 @@ int FontFaceHandleDefault::GenerateString(RenderManager& render_manager, Texture
 				continue;
 
 			// Adjust the cursor for the kerning between this character and the previous one.
-			line_width += GetKerning(prior_character, character, has_set_size);
+			if (is_kerning_enabled)
+				line_width += GetKerning(prior_character, character, has_set_size);
 
 			ColourbPremultiplied glyph_color = layer_colour;
 			// Use white vertex colors on RGB glyphs.
@@ -259,7 +264,7 @@ int FontFaceHandleDefault::GenerateString(RenderManager& render_manager, Texture
 			layer->GenerateGeometry(&mesh_list[geometry_index], character, Vector2f(position.x + line_width, position.y), glyph_color);
 
 			line_width += glyph->advance;
-			line_width += (int)letter_spacing;
+			line_width += (int)text_shaping_context.letter_spacing;
 			prior_character = character;
 		}
 
@@ -358,6 +363,11 @@ int FontFaceHandleDefault::GetKerning(Character lhs, Character rhs, bool& has_se
 	return result;
 }
 
+bool FontFaceHandleDefault::IsKerningEnabled(const TextShapingContext& text_shaping_context) const
+{
+	return text_shaping_context.font_kerning != Style::FontKerning::None;
+}
+
 const FontGlyph* FontFaceHandleDefault::GetOrAppendGlyph(Character& character, bool look_in_fallback_fonts)
 {
 	// Don't try to render control characters

+ 8 - 3
Source/Core/FontEngineDefault/FontFaceHandleDefault.h

@@ -34,6 +34,7 @@
 #include "../../../Include/RmlUi/Core/FontMetrics.h"
 #include "../../../Include/RmlUi/Core/Geometry.h"
 #include "../../../Include/RmlUi/Core/Texture.h"
+#include "../../../Include/RmlUi/Core/TextShapingContext.h"
 #include "../../../Include/RmlUi/Core/Traits.h"
 #include "FontTypes.h"
 
@@ -58,10 +59,11 @@ public:
 
 	/// Returns the width a string will take up if rendered with this handle.
 	/// @param[in] string The string to measure.
+	/// @param[in] text_shaping_context Extra parameters that provide context for text shaping.
 	/// @param[in] prior_character The optionally-specified character that immediately precedes the string. This may have an impact on the string
 	/// width due to kerning.
 	/// @return The width, in pixels, this string will occupy if rendered with this handle.
-	int GetStringWidth(StringView string, float letter_spacing, Character prior_character = Character::Null);
+	int GetStringWidth(StringView string, const TextShapingContext& text_shaping_context, Character prior_character = Character::Null);
 
 	/// Generates, if required, the layer configuration for a given list of font effects.
 	/// @param[in] font_effects The list of font effects to generate the configuration for.
@@ -83,11 +85,11 @@ public:
 	/// @param[in] position The position of the baseline of the first character to render.
 	/// @param[in] colour The colour to render the text.
 	/// @param[in] opacity The opacity of the text, should be applied to font effects.
-	/// @param[in] letter_spacing The letter spacing size in pixels.
+	/// @param[in] text_shaping_context Extra parameters that provide context for text shaping.
 	/// @param[in] layer_configuration Face configuration index to use for generating string.
 	/// @return The width, in pixels, of the string geometry.
 	int GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, StringView string, Vector2f position, ColourbPremultiplied colour,
-		float opacity, float letter_spacing, int layer_configuration);
+		float opacity, const TextShapingContext& text_shaping_context, int layer_configuration);
 
 	/// Version is changed whenever the layers are dirtied, requiring regeneration of string geometry.
 	int GetVersion() const;
@@ -102,6 +104,9 @@ private:
 	// Return the kerning for a character pair.
 	int GetKerning(Character lhs, Character rhs, bool& has_set_size) const;
 
+	// Returns whether kerning is enabled based on the text-shaping context.
+	bool IsKerningEnabled(const TextShapingContext& text_shaping_context) const;
+
 	/// Retrieve a glyph from the given code point, building and appending a new glyph if not already built.
 	/// @param[in-out] character  The character, 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 glyphs.

+ 1 - 0
Source/Core/StyleSheetSpecification.cpp

@@ -380,6 +380,7 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 	RegisterProperty(PropertyId::FontStyle, "font-style", "normal", true, true).AddParser("keyword", "normal, italic");
 	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::FontKerning, "font-kerning", "auto", true, true).AddParser("keyword", "auto, normal, none");
 	RegisterProperty(PropertyId::LetterSpacing, "letter-spacing", "normal", true, true).AddParser("keyword", "normal").AddParser("length");
 	RegisterShorthand(ShorthandId::Font, "font", "font-style, font-weight, font-size, font-family", ShorthandType::FallThrough);
 

+ 1 - 1
Tests/Tools/convert_css_test_suite_to_rml.py

@@ -341,7 +341,7 @@ def process_file(in_file):
 			return False
 
 		if match := re.search(r'(^|[^a-z\-])((direction:[^;]*[;"])|(content:[^;]*[;"])|(outline:[^;]*[;"])|(quote:[^;]*[;"])|(border-spacing:[^;]*[;"])|(border-collapse:[^;]*[;"])|(background:[^;]*[;"]))', line, flags = re.IGNORECASE)\
-			or re.search(r'\b((font-variant:[^;]*[;"])|(font-kerning:[^;]*[;"])|(font-feature-settings:[^;]*[;"])|(background-image:[^;]*[;"])|(caption-side:[^;]*[;"])|(clip:[^;]*[;"])|(page-break-inside:[^;]*[;"])|(word-spacing:[^;]*[;"]))', line, flags = re.IGNORECASE)\
+			or re.search(r'\b((font-variant:[^;]*[;"])|(font-feature-settings:[^;]*[;"])|(background-image:[^;]*[;"])|(caption-side:[^;]*[;"])|(clip:[^;]*[;"])|(page-break-inside:[^;]*[;"])|(word-spacing:[^;]*[;"]))', line, flags = re.IGNORECASE)\
 			or re.search(r'\b((writing-mode:[^;]*[;"])|(text-orientation:[^;]*[;"])|(text-indent:[^;]*[;"])|(page-break-after:[^;]*[;"])|(page-break-before:[^;]*[;"])|(column(?!-gap)[a-z\- ]*:[^;]*[;"])|(empty-cells:[^;]*[;"]))', line, flags = re.IGNORECASE)\
 			or re.search(r'\b((aspect-ratio:[^;]*[;"])|(place-items:[^;]*[;"])|(flex-flow:[^;]*[;"])|(order:[^;]*[;"])|([a-z\-]+:\s*calc\([^;]*[;"])|([a-z\-]+:\s*safe\b[^;]*[;"])|([a-z\-]+:\s*(min-|max-)?content\s*[;"]))', line, flags = re.IGNORECASE):
 			substring_max = lambda s, max_length: s[:max_length - 3] + '...' if len(s) > max_length else s