From 29199579f780f4f66350e1962926c08b2ddcf65b Mon Sep 17 00:00:00 2001
From: bruvzg <7645683+bruvzg@users.noreply.github.com>
Date: Mon, 10 Jan 2022 10:13:22 +0200
Subject: [PATCH] Add sub-pixel glyph positioning support.
---
doc/classes/FontData.xml | 3 +
doc/classes/ProjectSettings.xml | 9 +
doc/classes/TextServer.xml | 34 +++-
doc/classes/TextServerExtension.xml | 15 ++
editor/animation_track_editor.cpp | 6 +-
editor/editor_fonts.cpp | 67 +++----
editor/editor_settings.cpp | 2 +
editor/import/dynamicfont_import_settings.cpp | 7 +
.../import/resource_importer_dynamicfont.cpp | 6 +
editor/import/resource_importer_imagefont.cpp | 1 +
modules/text_server_adv/text_server_adv.cpp | 174 +++++++++++++++---
modules/text_server_adv/text_server_adv.h | 4 +
modules/text_server_fb/text_server_fb.cpp | 150 +++++++++++++--
modules/text_server_fb/text_server_fb.h | 4 +
scene/gui/code_edit.cpp | 2 +-
scene/gui/line_edit.cpp | 2 +-
scene/gui/text_edit.cpp | 2 +-
scene/register_scene_types.cpp | 13 +-
.../resources/default_theme/default_theme.cpp | 5 +-
scene/resources/default_theme/default_theme.h | 2 +-
scene/resources/font.cpp | 21 +++
scene/resources/font.h | 4 +
servers/text/text_server_extension.cpp | 15 ++
servers/text/text_server_extension.h | 5 +
servers/text_server.cpp | 11 +-
servers/text_server.h | 14 ++
tests/test_main.cpp | 2 +-
27 files changed, 496 insertions(+), 84 deletions(-)
diff --git a/doc/classes/FontData.xml b/doc/classes/FontData.xml
index 658f7dd34de..e52142e86c2 100644
--- a/doc/classes/FontData.xml
+++ b/doc/classes/FontData.xml
@@ -600,5 +600,8 @@
Font style name.
+
+ Font glyph sub-pixel positioning mode. Subpixel positioning provides shaper text and better kerning for smaller font sizes, at the cost of memory usage and font rasterization speed. Use [constant TextServer.SUBPIXEL_POSITIONING_AUTO] to automatically enable it based on the font size.
+
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 4b5f7b20910..5e2b19606dc 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -564,6 +564,15 @@
Path to a custom [Font] resource to use as default for all GUI elements of the project.
+
+ If set to [code]true[/code], default font uses 8-bit anitialiased glyph rendering. See [member FontData.antialiased].
+
+
+ Default font hinting mode. See [member FontData.hinting].
+
+
+ Default font glyph sub-pixel positioning mode. See [member FontData.subpixel_positioning].
+
diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml
index 9025e4ff2a2..e4d426deebd 100644
--- a/doc/classes/TextServer.xml
+++ b/doc/classes/TextServer.xml
@@ -328,6 +328,13 @@
Returns font style name.
+
+
+
+
+ Returns font sub-pixel glyph positioning mode.
+
+
@@ -613,7 +620,7 @@
-
+
Sets font hinting mode. Used by dynamic fonts only.
@@ -727,7 +734,15 @@
- Set the font style name.
+ Sets the font style name.
+
+
+
+
+
+
+
+ Sets font sub-pixel glyph positioning mode.
@@ -1493,6 +1508,21 @@
Use the default font hinting mode (crisper but less smooth).
+
+ Glyph horizontal position is rounded to the whole pixel size, each glyph is rasterized once.
+
+
+ Glyph horizontal position is rounded based on font size.
+ - To one quarter of the pixel size if font size is smaller or equal to [code]16[/code].
+ - To one half of the pixel size if font size is smaller or equal to [code]20[/code].
+ - To the whole pixel size for larger fonts.
+
+
+ Glyph horizontal position is rounded to one half of the pixel size, each glyph is rasterized up to two times.
+
+
+ Glyph horizontal position is rounded to one quarter of the pixel size, each glyph is rasterized up to four times.
+
TextServer supports bidirectional layouts.
diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml
index b212cec5f29..3b88728ef5a 100644
--- a/doc/classes/TextServerExtension.xml
+++ b/doc/classes/TextServerExtension.xml
@@ -328,6 +328,13 @@
Returns font style name.
+
+
+
+
+ Returns font sub-pixel glyph positioning mode.
+
+
@@ -738,6 +745,14 @@
Sets the font style name.
+
+
+
+
+
+ Sets font sub-pixel glyph positioning mode.
+
+
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 961eb907bbe..77813f2b659 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -1576,10 +1576,10 @@ void AnimationTimelineEdit::_notification(int p_what) {
int decimals = 2;
bool step_found = false;
- const int period_width = font->get_char_size('.', 0, font_size).width;
- int max_digit_width = font->get_char_size('0', 0, font_size).width;
+ const float period_width = font->get_char_size('.', 0, font_size).width;
+ float max_digit_width = font->get_char_size('0', 0, font_size).width;
for (int i = 1; i <= 9; i++) {
- const int digit_width = font->get_char_size('0' + i, 0, font_size).width;
+ const float digit_width = font->get_char_size('0' + i, 0, font_size).width;
max_digit_width = MAX(digit_width, max_digit_width);
}
const int max_sc = int(Math::ceil(zoomw / scale));
diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp
index dc84b99d04e..d80ee1d5691 100644
--- a/editor/editor_fonts.cpp
+++ b/editor/editor_fonts.cpp
@@ -145,7 +145,7 @@
m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \
MAKE_FALLBACKS(m_name);
-Ref load_cached_external_font(const String &p_path, TextServer::Hinting p_hinting, bool p_aa, bool p_autohint) {
+Ref load_cached_external_font(const String &p_path, TextServer::Hinting p_hinting, bool p_aa, bool p_autohint, TextServer::SubpixelPositioning p_font_subpixel_positioning) {
Ref font;
font.instantiate();
@@ -155,11 +155,12 @@ Ref load_cached_external_font(const String &p_path, TextServer::Hintin
font->set_antialiased(p_aa);
font->set_hinting(p_hinting);
font->set_force_autohinter(p_autohint);
+ font->set_subpixel_positioning(p_font_subpixel_positioning);
return font;
}
-Ref load_cached_internal_font(const uint8_t *p_data, size_t p_size, TextServer::Hinting p_hinting, bool p_aa, bool p_autohint) {
+Ref load_cached_internal_font(const uint8_t *p_data, size_t p_size, TextServer::Hinting p_hinting, bool p_aa, bool p_autohint, TextServer::SubpixelPositioning p_font_subpixel_positioning) {
Ref font;
font.instantiate();
@@ -167,6 +168,7 @@ Ref load_cached_internal_font(const uint8_t *p_data, size_t p_size, Te
font->set_antialiased(p_aa);
font->set_hinting(p_hinting);
font->set_force_autohinter(p_autohint);
+ font->set_subpixel_positioning(p_font_subpixel_positioning);
return font;
}
@@ -178,6 +180,7 @@ void editor_register_fonts(Ref p_theme) {
bool font_antialiased = (bool)EditorSettings::get_singleton()->get("interface/editor/font_antialiased");
int font_hinting_setting = (int)EditorSettings::get_singleton()->get("interface/editor/font_hinting");
+ TextServer::SubpixelPositioning font_subpixel_positioning = (TextServer::SubpixelPositioning)(int)EditorSettings::get_singleton()->get("interface/editor/font_subpixel_positioning");
TextServer::Hinting font_hinting;
switch (font_hinting_setting) {
@@ -208,7 +211,7 @@ void editor_register_fonts(Ref p_theme) {
String custom_font_path = EditorSettings::get_singleton()->get("interface/editor/main_font");
Ref CustomFont;
if (custom_font_path.length() > 0 && dir->file_exists(custom_font_path)) {
- CustomFont = load_cached_external_font(custom_font_path, font_hinting, font_antialiased, true);
+ CustomFont = load_cached_external_font(custom_font_path, font_hinting, font_antialiased, true, font_subpixel_positioning);
} else {
EditorSettings::get_singleton()->set_manually("interface/editor/main_font", "");
}
@@ -218,7 +221,7 @@ void editor_register_fonts(Ref p_theme) {
String custom_font_path_bold = EditorSettings::get_singleton()->get("interface/editor/main_font_bold");
Ref CustomFontBold;
if (custom_font_path_bold.length() > 0 && dir->file_exists(custom_font_path_bold)) {
- CustomFontBold = load_cached_external_font(custom_font_path_bold, font_hinting, font_antialiased, true);
+ CustomFontBold = load_cached_external_font(custom_font_path_bold, font_hinting, font_antialiased, true, font_subpixel_positioning);
} else {
EditorSettings::get_singleton()->set_manually("interface/editor/main_font_bold", "");
}
@@ -228,7 +231,7 @@ void editor_register_fonts(Ref p_theme) {
String custom_font_path_source = EditorSettings::get_singleton()->get("interface/editor/code_font");
Ref CustomFontSource;
if (custom_font_path_source.length() > 0 && dir->file_exists(custom_font_path_source)) {
- CustomFontSource = load_cached_external_font(custom_font_path_source, font_hinting, font_antialiased, true);
+ CustomFontSource = load_cached_external_font(custom_font_path_source, font_hinting, font_antialiased, true, font_subpixel_positioning);
} else {
EditorSettings::get_singleton()->set_manually("interface/editor/code_font", "");
}
@@ -237,39 +240,39 @@ void editor_register_fonts(Ref p_theme) {
/* Noto Sans */
- Ref DefaultFont = load_cached_internal_font(_font_NotoSans_Regular, _font_NotoSans_Regular_size, font_hinting, font_antialiased, true);
- Ref DefaultFontBold = load_cached_internal_font(_font_NotoSans_Bold, _font_NotoSans_Bold_size, font_hinting, font_antialiased, true);
- Ref FontArabic = load_cached_internal_font(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, font_hinting, font_antialiased, true);
- Ref FontArabicBold = load_cached_internal_font(_font_NotoNaskhArabicUI_Bold, _font_NotoNaskhArabicUI_Bold_size, font_hinting, font_antialiased, true);
- Ref FontBengali = load_cached_internal_font(_font_NotoSansBengaliUI_Regular, _font_NotoSansBengaliUI_Regular_size, font_hinting, font_antialiased, true);
- Ref FontBengaliBold = load_cached_internal_font(_font_NotoSansBengaliUI_Bold, _font_NotoSansBengaliUI_Bold_size, font_hinting, font_antialiased, true);
- Ref FontDevanagari = load_cached_internal_font(_font_NotoSansDevanagariUI_Regular, _font_NotoSansDevanagariUI_Regular_size, font_hinting, font_antialiased, true);
- Ref FontDevanagariBold = load_cached_internal_font(_font_NotoSansDevanagariUI_Bold, _font_NotoSansDevanagariUI_Bold_size, font_hinting, font_antialiased, true);
- Ref FontGeorgian = load_cached_internal_font(_font_NotoSansGeorgian_Regular, _font_NotoSansGeorgian_Regular_size, font_hinting, font_antialiased, true);
- Ref FontGeorgianBold = load_cached_internal_font(_font_NotoSansGeorgian_Bold, _font_NotoSansGeorgian_Bold_size, font_hinting, font_antialiased, true);
- Ref FontHebrew = load_cached_internal_font(_font_NotoSansHebrew_Regular, _font_NotoSansHebrew_Regular_size, font_hinting, font_antialiased, true);
- Ref FontHebrewBold = load_cached_internal_font(_font_NotoSansHebrew_Bold, _font_NotoSansHebrew_Bold_size, font_hinting, font_antialiased, true);
- Ref FontMalayalam = load_cached_internal_font(_font_NotoSansMalayalamUI_Regular, _font_NotoSansMalayalamUI_Regular_size, font_hinting, font_antialiased, true);
- Ref FontMalayalamBold = load_cached_internal_font(_font_NotoSansMalayalamUI_Bold, _font_NotoSansMalayalamUI_Bold_size, font_hinting, font_antialiased, true);
- Ref FontOriya = load_cached_internal_font(_font_NotoSansOriyaUI_Regular, _font_NotoSansOriyaUI_Regular_size, font_hinting, font_antialiased, true);
- Ref FontOriyaBold = load_cached_internal_font(_font_NotoSansOriyaUI_Bold, _font_NotoSansOriyaUI_Bold_size, font_hinting, font_antialiased, true);
- Ref FontSinhala = load_cached_internal_font(_font_NotoSansSinhalaUI_Regular, _font_NotoSansSinhalaUI_Regular_size, font_hinting, font_antialiased, true);
- Ref FontSinhalaBold = load_cached_internal_font(_font_NotoSansSinhalaUI_Bold, _font_NotoSansSinhalaUI_Bold_size, font_hinting, font_antialiased, true);
- Ref FontTamil = load_cached_internal_font(_font_NotoSansTamilUI_Regular, _font_NotoSansTamilUI_Regular_size, font_hinting, font_antialiased, true);
- Ref FontTamilBold = load_cached_internal_font(_font_NotoSansTamilUI_Bold, _font_NotoSansTamilUI_Bold_size, font_hinting, font_antialiased, true);
- Ref FontTelugu = load_cached_internal_font(_font_NotoSansTeluguUI_Regular, _font_NotoSansTeluguUI_Regular_size, font_hinting, font_antialiased, true);
- Ref FontTeluguBold = load_cached_internal_font(_font_NotoSansTeluguUI_Bold, _font_NotoSansTeluguUI_Bold_size, font_hinting, font_antialiased, true);
- Ref FontThai = load_cached_internal_font(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, font_hinting, font_antialiased, true);
- Ref FontThaiBold = load_cached_internal_font(_font_NotoSansThaiUI_Bold, _font_NotoSansThaiUI_Bold_size, font_hinting, font_antialiased, true);
+ Ref DefaultFont = load_cached_internal_font(_font_NotoSans_Regular, _font_NotoSans_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref DefaultFontBold = load_cached_internal_font(_font_NotoSans_Bold, _font_NotoSans_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontArabic = load_cached_internal_font(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontArabicBold = load_cached_internal_font(_font_NotoNaskhArabicUI_Bold, _font_NotoNaskhArabicUI_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontBengali = load_cached_internal_font(_font_NotoSansBengaliUI_Regular, _font_NotoSansBengaliUI_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontBengaliBold = load_cached_internal_font(_font_NotoSansBengaliUI_Bold, _font_NotoSansBengaliUI_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontDevanagari = load_cached_internal_font(_font_NotoSansDevanagariUI_Regular, _font_NotoSansDevanagariUI_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontDevanagariBold = load_cached_internal_font(_font_NotoSansDevanagariUI_Bold, _font_NotoSansDevanagariUI_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontGeorgian = load_cached_internal_font(_font_NotoSansGeorgian_Regular, _font_NotoSansGeorgian_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontGeorgianBold = load_cached_internal_font(_font_NotoSansGeorgian_Bold, _font_NotoSansGeorgian_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontHebrew = load_cached_internal_font(_font_NotoSansHebrew_Regular, _font_NotoSansHebrew_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontHebrewBold = load_cached_internal_font(_font_NotoSansHebrew_Bold, _font_NotoSansHebrew_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontMalayalam = load_cached_internal_font(_font_NotoSansMalayalamUI_Regular, _font_NotoSansMalayalamUI_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontMalayalamBold = load_cached_internal_font(_font_NotoSansMalayalamUI_Bold, _font_NotoSansMalayalamUI_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontOriya = load_cached_internal_font(_font_NotoSansOriyaUI_Regular, _font_NotoSansOriyaUI_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontOriyaBold = load_cached_internal_font(_font_NotoSansOriyaUI_Bold, _font_NotoSansOriyaUI_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontSinhala = load_cached_internal_font(_font_NotoSansSinhalaUI_Regular, _font_NotoSansSinhalaUI_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontSinhalaBold = load_cached_internal_font(_font_NotoSansSinhalaUI_Bold, _font_NotoSansSinhalaUI_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontTamil = load_cached_internal_font(_font_NotoSansTamilUI_Regular, _font_NotoSansTamilUI_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontTamilBold = load_cached_internal_font(_font_NotoSansTamilUI_Bold, _font_NotoSansTamilUI_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontTelugu = load_cached_internal_font(_font_NotoSansTeluguUI_Regular, _font_NotoSansTeluguUI_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontTeluguBold = load_cached_internal_font(_font_NotoSansTeluguUI_Bold, _font_NotoSansTeluguUI_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontThai = load_cached_internal_font(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontThaiBold = load_cached_internal_font(_font_NotoSansThaiUI_Bold, _font_NotoSansThaiUI_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
/* Droid Sans */
- Ref FontFallback = load_cached_internal_font(_font_DroidSansFallback, _font_DroidSansFallback_size, font_hinting, font_antialiased, true);
- Ref FontJapanese = load_cached_internal_font(_font_DroidSansJapanese, _font_DroidSansJapanese_size, font_hinting, font_antialiased, true);
+ Ref FontFallback = load_cached_internal_font(_font_DroidSansFallback, _font_DroidSansFallback_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
+ Ref FontJapanese = load_cached_internal_font(_font_DroidSansJapanese, _font_DroidSansJapanese_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
/* Hack */
- Ref dfmono = load_cached_internal_font(_font_JetBrainsMono_Regular, _font_JetBrainsMono_Regular_size, font_hinting, font_antialiased, true);
+ Ref dfmono = load_cached_internal_font(_font_JetBrainsMono_Regular, _font_JetBrainsMono_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
Dictionary opentype_features;
opentype_features["calt"] = 0;
dfmono->set_opentype_feature_overrides(opentype_features); // Disable contextual alternates (coding ligatures).
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index 0c3f7287a50..84c05cb4294 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -415,6 +415,8 @@ void EditorSettings::_load_defaults(Ref p_extra_config) {
#else
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/font_hinting", 0, "Auto (Light),None,Light,Normal")
#endif
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/font_subpixel_positioning", 1, "Disabled,Auto,One half of a pixel,One quarter of a pixel")
+
EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/main_font", "", "*.ttf,*.otf")
EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/main_font_bold", "", "*.ttf,*.otf")
EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "interface/editor/code_font", "", "*.ttf,*.otf")
diff --git a/editor/import/dynamicfont_import_settings.cpp b/editor/import/dynamicfont_import_settings.cpp
index 33c861ba247..ead3d67e39d 100644
--- a/editor/import/dynamicfont_import_settings.cpp
+++ b/editor/import/dynamicfont_import_settings.cpp
@@ -480,6 +480,10 @@ void DynamicFontImportSettings::_main_prop_changed(const String &p_edited_proper
if (font_preview->get_data_count() > 0) {
font_preview->get_data(0)->set_hinting((TextServer::Hinting)import_settings_data->get("hinting").operator int());
}
+ } else if (p_edited_property == "subpixel_positioning") {
+ if (font_preview->get_data_count() > 0) {
+ font_preview->get_data(0)->set_subpixel_positioning((TextServer::SubpixelPositioning)import_settings_data->get("subpixel_positioning").operator int());
+ }
} else if (p_edited_property == "oversampling") {
if (font_preview->get_data_count() > 0) {
font_preview->get_data(0)->set_oversampling(import_settings_data->get("oversampling"));
@@ -915,6 +919,7 @@ void DynamicFontImportSettings::_re_import() {
main_settings["msdf_size"] = import_settings_data->get("msdf_size");
main_settings["force_autohinter"] = import_settings_data->get("force_autohinter");
main_settings["hinting"] = import_settings_data->get("hinting");
+ main_settings["subpixel_positioning"] = import_settings_data->get("subpixel_positioning");
main_settings["oversampling"] = import_settings_data->get("oversampling");
main_settings["compress"] = import_settings_data->get("compress");
@@ -1265,6 +1270,7 @@ void DynamicFontImportSettings::open_settings(const String &p_path) {
font_preview->get_data(0)->set_msdf_size(import_settings_data->get("msdf_size"));
font_preview->get_data(0)->set_force_autohinter(import_settings_data->get("force_autohinter"));
font_preview->get_data(0)->set_hinting((TextServer::Hinting)import_settings_data->get("hinting").operator int());
+ font_preview->get_data(0)->set_subpixel_positioning((TextServer::SubpixelPositioning)import_settings_data->get("subpixel_positioning").operator int());
font_preview->get_data(0)->set_oversampling(import_settings_data->get("oversampling"));
}
font_preview_label->add_theme_font_override("font", font_preview);
@@ -1323,6 +1329,7 @@ DynamicFontImportSettings::DynamicFontImportSettings() {
options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_RANGE, "1,250,1"), 48));
options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false));
options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1));
+ options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel"), 1));
options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), 0.0));
options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "compress", PROPERTY_HINT_NONE, ""), false));
diff --git a/editor/import/resource_importer_dynamicfont.cpp b/editor/import/resource_importer_dynamicfont.cpp
index 11f563a9827..42be76ba121 100644
--- a/editor/import/resource_importer_dynamicfont.cpp
+++ b/editor/import/resource_importer_dynamicfont.cpp
@@ -76,6 +76,9 @@ bool ResourceImporterDynamicFont::get_option_visibility(const String &p_path, co
if (p_option == "oversampling" && bool(p_options["multichannel_signed_distance_field"])) {
return false;
}
+ if (p_option == "subpixel_positioning" && bool(p_options["multichannel_signed_distance_field"])) {
+ return false;
+ }
return true;
}
@@ -104,6 +107,7 @@ void ResourceImporterDynamicFont::get_import_options(const String &p_path, List<
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel"), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), 0.0));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true));
@@ -179,6 +183,7 @@ Error ResourceImporterDynamicFont::import(const String &p_source_file, const Str
bool autohinter = p_options["force_autohinter"];
int hinting = p_options["hinting"];
+ int subpixel_positioning = p_options["subpixel_positioning"];
real_t oversampling = p_options["oversampling"];
// Load base font data.
@@ -195,6 +200,7 @@ Error ResourceImporterDynamicFont::import(const String &p_source_file, const Str
font->set_opentype_feature_overrides(ot_ov);
font->set_fixed_size(0);
font->set_force_autohinter(autohinter);
+ font->set_subpixel_positioning((TextServer::SubpixelPositioning)subpixel_positioning);
font->set_hinting((TextServer::Hinting)hinting);
font->set_oversampling(oversampling);
diff --git a/editor/import/resource_importer_imagefont.cpp b/editor/import/resource_importer_imagefont.cpp
index 0a15284ef52..1338cf03a85 100644
--- a/editor/import/resource_importer_imagefont.cpp
+++ b/editor/import/resource_importer_imagefont.cpp
@@ -98,6 +98,7 @@ Error ResourceImporterImageFont::import(const String &p_source_file, const Strin
font->set_antialiased(false);
font->set_multichannel_signed_distance_field(false);
font->set_fixed_size(base_size);
+ font->set_subpixel_positioning(TextServer::SUBPIXEL_POSITIONING_DISABLED);
font->set_force_autohinter(false);
font->set_hinting(TextServer::HINTING_NONE);
font->set_oversampling(1.0f);
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index c7511f587e6..1b4512dc609 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -1097,12 +1097,14 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_bitma
_FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontDataAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const {
ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false);
+ int32_t glyph_index = p_glyph & 0xFFFFFF; // Remove subpixel shifts.
+
FontDataForSizeAdvanced *fd = p_font_data->cache[p_size];
if (fd->glyph_map.has(p_glyph)) {
return fd->glyph_map[p_glyph].found;
}
- if (p_glyph == 0) { // Non graphical or invalid glyph, do not render.
+ if (glyph_index == 0) { // Non graphical or invalid glyph, do not render.
fd->glyph_map[p_glyph] = FontGlyph();
return true;
}
@@ -1134,15 +1136,25 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontDataAdvanced *p_font_d
}
FT_Fixed v, h;
- FT_Get_Advance(fd->face, p_glyph, flags, &h);
- FT_Get_Advance(fd->face, p_glyph, flags | FT_LOAD_VERTICAL_LAYOUT, &v);
+ FT_Get_Advance(fd->face, glyph_index, flags, &h);
+ FT_Get_Advance(fd->face, glyph_index, flags | FT_LOAD_VERTICAL_LAYOUT, &v);
- int error = FT_Load_Glyph(fd->face, p_glyph, flags);
+ int error = FT_Load_Glyph(fd->face, glyph_index, flags);
if (error) {
fd->glyph_map[p_glyph] = FontGlyph();
return false;
}
+ if (!p_font_data->msdf) {
+ if ((p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && p_size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ FT_Pos xshift = (int)((p_glyph >> 27) & 3) << 4;
+ FT_Outline_Translate(&fd->face->glyph->outline, xshift, 0);
+ } else if ((p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && p_size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ FT_Pos xshift = (int)((p_glyph >> 27) & 3) << 5;
+ FT_Outline_Translate(&fd->face->glyph->outline, xshift, 0);
+ }
+ }
+
if (!outline) {
if (!p_font_data->msdf) {
error = FT_Render_Glyph(fd->face->glyph, p_font_data->antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
@@ -1844,6 +1856,24 @@ TextServer::Hinting TextServerAdvanced::font_get_hinting(RID p_font_rid) const {
return fd->hinting;
}
+void TextServerAdvanced::font_set_subpixel_positioning(RID p_font_rid, TextServer::SubpixelPositioning p_subpixel) {
+ FontDataAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_COND(!fd);
+
+ MutexLock lock(fd->mutex);
+ if (fd->subpixel_positioning != p_subpixel) {
+ fd->subpixel_positioning = p_subpixel;
+ }
+}
+
+TextServer::SubpixelPositioning TextServerAdvanced::font_get_subpixel_positioning(RID p_font_rid) const {
+ FontDataAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_COND_V(!fd, SUBPIXEL_POSITIONING_DISABLED);
+
+ MutexLock lock(fd->mutex);
+ return fd->subpixel_positioning;
+}
+
void TextServerAdvanced::font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) {
FontDataAdvanced *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
@@ -2261,6 +2291,8 @@ Vector2 TextServerAdvanced::font_get_glyph_advance(RID p_font_rid, int p_size, i
if (fd->msdf) {
return gl[p_glyph].advance * (float)p_size / (float)fd->msdf_source_size;
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ return gl[p_glyph].advance.round();
} else {
return gl[p_glyph].advance;
}
@@ -2628,12 +2660,25 @@ void TextServerAdvanced::font_render_range(RID p_font_rid, const Vector2i &p_siz
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
for (char32_t i = p_start; i <= p_end; i++) {
#ifdef MODULE_FREETYPE_ENABLED
+ int32_t idx = FT_Get_Char_Index(fd->cache[size]->face, i);
if (fd->cache[size]->face) {
- _ensure_glyph(fd, size, FT_Get_Char_Index(fd->cache[size]->face, i));
- continue;
+ if (fd->msdf) {
+ _ensure_glyph(fd, size, (int32_t)idx);
+ } else {
+ if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27));
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27));
+ } else {
+ _ensure_glyph(fd, size, (int32_t)idx);
+ }
+ }
}
#endif
- _ensure_glyph(fd, size, (int32_t)i);
}
}
@@ -2644,7 +2689,26 @@ void TextServerAdvanced::font_render_glyph(RID p_font_rid, const Vector2i &p_siz
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- ERR_FAIL_COND(!_ensure_glyph(fd, size, p_index));
+#ifdef MODULE_FREETYPE_ENABLED
+ int32_t idx = p_index;
+ if (fd->cache[size]->face) {
+ if (fd->msdf) {
+ _ensure_glyph(fd, size, (int32_t)idx);
+ } else {
+ if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27));
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27));
+ } else {
+ _ensure_glyph(fd, size, (int32_t)idx);
+ }
+ }
+ }
+#endif
}
void TextServerAdvanced::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color) const {
@@ -2654,11 +2718,26 @@ void TextServerAdvanced::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_siz
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- if (!_ensure_glyph(fd, size, p_index)) {
+
+ int32_t index = p_index;
+
+#ifdef MODULE_FREETYPE_ENABLED
+ if (!fd->msdf && fd->cache[size]->face) {
+ if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ int xshift = (int)(Math::floor(4 * (p_pos.x + 0.125)) - 4 * Math::floor(p_pos.x + 0.125));
+ index = index | (xshift << 27);
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ int xshift = (int)(Math::floor(2 * (p_pos.x + 0.25)) - 2 * Math::floor(p_pos.x + 0.25));
+ index = index | (xshift << 27);
+ }
+ }
+#endif
+
+ if (!_ensure_glyph(fd, size, index)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[p_index];
+ const FontGlyph &gl = fd->cache[size]->glyph_map[index];
if (gl.found) {
ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
@@ -2677,7 +2756,15 @@ void TextServerAdvanced::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_siz
Size2 csize = gl.rect.size * (float)p_size / (float)fd->msdf_source_size;
RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range);
} else {
- Point2 cpos = p_pos.floor();
+ Point2 cpos = p_pos;
+ cpos.y = Math::floor(cpos.y);
+ if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ cpos.x = ((int)Math::floor(cpos.x + 0.125));
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ cpos.x = ((int)Math::floor(cpos.x + 0.25));
+ } else {
+ cpos.x = Math::floor(cpos.x);
+ }
cpos += gl.rect.position;
Size2 csize = gl.rect.size;
RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
@@ -2694,11 +2781,26 @@ void TextServerAdvanced::font_draw_glyph_outline(RID p_font_rid, RID p_canvas, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size));
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- if (!_ensure_glyph(fd, size, p_index)) {
+
+ int32_t index = p_index;
+
+#ifdef MODULE_FREETYPE_ENABLED
+ if (!fd->msdf && fd->cache[size]->face) {
+ if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ int xshift = (int)(Math::floor(4 * (p_pos.x + 0.125)) - 4 * Math::floor(p_pos.x + 0.125));
+ index = index | (xshift << 27);
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ int xshift = (int)(Math::floor(2 * (p_pos.x + 0.25)) - 2 * Math::floor(p_pos.x + 0.25));
+ index = index | (xshift << 27);
+ }
+ }
+#endif
+
+ if (!_ensure_glyph(fd, size, index)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[p_index];
+ const FontGlyph &gl = fd->cache[size]->glyph_map[index];
if (gl.found) {
ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
@@ -2717,7 +2819,15 @@ void TextServerAdvanced::font_draw_glyph_outline(RID p_font_rid, RID p_canvas, i
Size2 csize = gl.rect.size * (float)p_size / (float)fd->msdf_source_size;
RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size * 2, fd->msdf_range);
} else {
- Point2 cpos = p_pos.floor();
+ Point2 cpos = p_pos;
+ cpos.y = Math::floor(cpos.y);
+ if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ cpos.x = ((int)Math::floor(cpos.x + 0.125));
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ cpos.x = ((int)Math::floor(cpos.x + 0.25));
+ } else {
+ cpos.x = Math::floor(cpos.x);
+ }
cpos += gl.rect.position;
Size2 csize = gl.rect.size;
RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
@@ -3559,7 +3669,7 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width,
}
justification_width = sd->width_trimmed;
} else {
- return sd->width;
+ return Math::ceil(sd->width);
}
} else {
justification_width = sd->width;
@@ -3662,7 +3772,7 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width,
sd->width = justification_width;
}
- return justification_width;
+ return Math::ceil(justification_width);
}
float TextServerAdvanced::shaped_text_tab_align(RID p_shaped, const PackedFloat32Array &p_tab_stops) {
@@ -4281,6 +4391,7 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) {
Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char32_t p_char, hb_script_t p_script, hb_direction_t p_direction, RID p_font, int p_font_size) {
hb_font_t *hb_font = _font_get_hb_handle(p_font, p_font_size);
+ bool subpos = (font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_HALF) || (font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_AUTO && p_font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
ERR_FAIL_COND_V(hb_font == nullptr, Glyph());
hb_buffer_clear_contents(p_sd->hb_buffer);
@@ -4308,14 +4419,22 @@ Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char
if (glyph_count > 0) {
float scale = font_get_scale(p_font, p_font_size);
if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
- gl.advance = Math::round(glyph_pos[0].x_advance / (64.0 / scale));
+ if (subpos) {
+ gl.advance = glyph_pos[0].x_advance / (64.0 / scale);
+ } else {
+ gl.advance = Math::round(glyph_pos[0].x_advance / (64.0 / scale));
+ }
} else {
gl.advance = -Math::round(glyph_pos[0].y_advance / (64.0 / scale));
}
gl.count = 1;
gl.index = glyph_info[0].codepoint;
- gl.x_off = Math::round(glyph_pos[0].x_offset / (64.0 / scale));
+ if (subpos) {
+ gl.x_off = glyph_pos[0].x_offset / (64.0 / scale);
+ } else {
+ gl.x_off = Math::round(glyph_pos[0].x_offset / (64.0 / scale));
+ }
gl.y_off = -Math::round(glyph_pos[0].y_offset / (64.0 / scale));
if ((glyph_info[0].codepoint != 0) || !u_isgraph(p_char)) {
@@ -4380,6 +4499,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star
float scale = font_get_scale(f, fs);
float sp_sp = font_get_spacing(f, fs, SPACING_SPACE);
float sp_gl = font_get_spacing(f, fs, SPACING_GLYPH);
+ bool subpos = (font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_HALF) || (font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_AUTO && fs <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
ERR_FAIL_COND(hb_font == nullptr);
hb_buffer_clear_contents(p_sd->hb_buffer);
@@ -4456,11 +4576,19 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star
gl.index = glyph_info[i].codepoint;
if (gl.index != 0) {
if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
- gl.advance = Math::round(glyph_pos[i].x_advance / (64.0 / scale));
+ if (subpos) {
+ gl.advance = glyph_pos[i].x_advance / (64.0 / scale);
+ } else {
+ gl.advance = Math::round(glyph_pos[i].x_advance / (64.0 / scale));
+ }
} else {
gl.advance = -Math::round(glyph_pos[i].y_advance / (64.0 / scale));
}
- gl.x_off = Math::round(glyph_pos[i].x_offset / (64.0 / scale));
+ if (subpos) {
+ gl.x_off = glyph_pos[i].x_offset / (64.0 / scale);
+ } else {
+ gl.x_off = Math::round(glyph_pos[i].x_offset / (64.0 / scale));
+ }
gl.y_off = -Math::round(glyph_pos[i].y_offset / (64.0 / scale));
}
if (sp_sp && is_whitespace(p_sd->text[glyph_info[i].cluster])) {
@@ -4797,9 +4925,9 @@ Size2 TextServerAdvanced::shaped_text_get_size(RID p_shaped) const {
const_cast(this)->shaped_text_shape(p_shaped);
}
if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) {
- return Size2((sd->text_trimmed ? sd->width_trimmed : sd->width), sd->ascent + sd->descent);
+ return Size2((sd->text_trimmed ? sd->width_trimmed : sd->width), sd->ascent + sd->descent).ceil();
} else {
- return Size2(sd->ascent + sd->descent, (sd->text_trimmed ? sd->width_trimmed : sd->width));
+ return Size2(sd->ascent + sd->descent, (sd->text_trimmed ? sd->width_trimmed : sd->width)).ceil();
}
}
@@ -4833,7 +4961,7 @@ float TextServerAdvanced::shaped_text_get_width(RID p_shaped) const {
if (!sd->valid) {
const_cast(this)->shaped_text_shape(p_shaped);
}
- return (sd->text_trimmed ? sd->width_trimmed : sd->width);
+ return Math::ceil(sd->text_trimmed ? sd->width_trimmed : sd->width);
}
float TextServerAdvanced::shaped_text_get_underline_position(RID p_shaped) const {
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index 145d740b683..7841a15cd3b 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -174,6 +174,7 @@ class TextServerAdvanced : public TextServer {
int fixed_size = 0;
bool force_autohinter = false;
TextServer::Hinting hinting = TextServer::HINTING_LIGHT;
+ TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO;
Dictionary variation_coordinates;
float oversampling = 0.f;
@@ -379,6 +380,9 @@ public:
virtual void font_set_hinting(RID p_font_rid, TextServer::Hinting p_hinting) override;
virtual TextServer::Hinting font_get_hinting(RID p_font_rid) const override;
+ virtual void font_set_subpixel_positioning(RID p_font_rid, SubpixelPositioning p_subpixel) override;
+ virtual SubpixelPositioning font_get_subpixel_positioning(RID p_font_rid) const override;
+
virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) override;
virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const override;
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 182d2a02ad3..e6f9bcf131d 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -548,12 +548,14 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_bitma
_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontDataFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const {
ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false);
+ int32_t glyph_index = p_glyph & 0xFFFFFF; // Remove subpixel shifts.
+
FontDataForSizeFallback *fd = p_font_data->cache[p_size];
if (fd->glyph_map.has(p_glyph)) {
return fd->glyph_map[p_glyph].found;
}
- if (p_glyph == 0) { // Non graphical or invalid glyph, do not render.
+ if (glyph_index == 0) { // Non graphical or invalid glyph, do not render.
fd->glyph_map[p_glyph] = FontGlyph();
return true;
}
@@ -584,8 +586,6 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontDataFallback *p_font_d
flags |= FT_LOAD_COLOR;
}
- int32_t glyph_index = FT_Get_Char_Index(fd->face, p_glyph);
-
FT_Fixed v, h;
FT_Get_Advance(fd->face, glyph_index, flags, &h);
FT_Get_Advance(fd->face, glyph_index, flags | FT_LOAD_VERTICAL_LAYOUT, &v);
@@ -596,6 +596,16 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontDataFallback *p_font_d
return false;
}
+ if (!p_font_data->msdf) {
+ if ((p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && p_size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ FT_Pos xshift = (int)((p_glyph >> 27) & 3) << 4;
+ FT_Outline_Translate(&fd->face->glyph->outline, xshift, 0);
+ } else if ((p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && p_size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ FT_Pos xshift = (int)((p_glyph >> 27) & 3) << 5;
+ FT_Outline_Translate(&fd->face->glyph->outline, xshift, 0);
+ }
+ }
+
if (!outline) {
if (!p_font_data->msdf) {
error = FT_Render_Glyph(fd->face->glyph, p_font_data->antialiased ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
@@ -1016,6 +1026,24 @@ TextServer::Hinting TextServerFallback::font_get_hinting(RID p_font_rid) const {
return fd->hinting;
}
+void TextServerFallback::font_set_subpixel_positioning(RID p_font_rid, TextServer::SubpixelPositioning p_subpixel) {
+ FontDataFallback *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_COND(!fd);
+
+ MutexLock lock(fd->mutex);
+ if (fd->subpixel_positioning != p_subpixel) {
+ fd->subpixel_positioning = p_subpixel;
+ }
+}
+
+TextServer::SubpixelPositioning TextServerFallback::font_get_subpixel_positioning(RID p_font_rid) const {
+ FontDataFallback *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_COND_V(!fd, SUBPIXEL_POSITIONING_DISABLED);
+
+ MutexLock lock(fd->mutex);
+ return fd->subpixel_positioning;
+}
+
void TextServerFallback::font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) {
FontDataFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
@@ -1433,6 +1461,8 @@ Vector2 TextServerFallback::font_get_glyph_advance(RID p_font_rid, int p_size, i
if (fd->msdf) {
return gl[p_glyph].advance * (float)p_size / (float)fd->msdf_source_size;
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ return gl[p_glyph].advance.round();
} else {
return gl[p_glyph].advance;
}
@@ -1781,7 +1811,26 @@ void TextServerFallback::font_render_range(RID p_font_rid, const Vector2i &p_siz
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
for (char32_t i = p_start; i <= p_end; i++) {
- _ensure_glyph(fd, size, (int32_t)i);
+#ifdef MODULE_FREETYPE_ENABLED
+ int32_t idx = i;
+ if (fd->cache[size]->face) {
+ if (fd->msdf) {
+ _ensure_glyph(fd, size, (int32_t)idx);
+ } else {
+ if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27));
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27));
+ } else {
+ _ensure_glyph(fd, size, (int32_t)idx);
+ }
+ }
+ }
+#endif
}
}
@@ -1792,7 +1841,26 @@ void TextServerFallback::font_render_glyph(RID p_font_rid, const Vector2i &p_siz
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- ERR_FAIL_COND(!_ensure_glyph(fd, size, p_index));
+#ifdef MODULE_FREETYPE_ENABLED
+ int32_t idx = p_index;
+ if (fd->cache[size]->face) {
+ if (fd->msdf) {
+ _ensure_glyph(fd, size, (int32_t)idx);
+ } else {
+ if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27));
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27));
+ } else {
+ _ensure_glyph(fd, size, (int32_t)idx);
+ }
+ }
+ }
+#endif
}
void TextServerFallback::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_size, const Vector2 &p_pos, int32_t p_index, const Color &p_color) const {
@@ -1802,11 +1870,26 @@ void TextServerFallback::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_siz
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- if (!_ensure_glyph(fd, size, p_index)) {
+
+ int32_t index = p_index;
+
+#ifdef MODULE_FREETYPE_ENABLED
+ if (!fd->msdf && fd->cache[size]->face) {
+ if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ int xshift = (int)(Math::floor(4 * (p_pos.x + 0.125)) - 4 * Math::floor(p_pos.x + 0.125));
+ index = index | (xshift << 27);
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ int xshift = (int)(Math::floor(2 * (p_pos.x + 0.25)) - 2 * Math::floor(p_pos.x + 0.25));
+ index = index | (xshift << 27);
+ }
+ }
+#endif
+
+ if (!_ensure_glyph(fd, size, index)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[p_index];
+ const FontGlyph &gl = fd->cache[size]->glyph_map[index];
if (gl.found) {
ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
@@ -1825,7 +1908,15 @@ void TextServerFallback::font_draw_glyph(RID p_font_rid, RID p_canvas, int p_siz
Size2 csize = gl.rect.size * (float)p_size / (float)fd->msdf_source_size;
RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range);
} else {
- Point2 cpos = p_pos.floor();
+ Point2 cpos = p_pos;
+ cpos.y = Math::floor(cpos.y);
+ if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ cpos.x = ((int)Math::floor(cpos.x + 0.125));
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ cpos.x = ((int)Math::floor(cpos.x + 0.25));
+ } else {
+ cpos.x = Math::floor(cpos.x);
+ }
cpos += gl.rect.position;
Size2 csize = gl.rect.size;
RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
@@ -1842,11 +1933,26 @@ void TextServerFallback::font_draw_glyph_outline(RID p_font_rid, RID p_canvas, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size));
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- if (!_ensure_glyph(fd, size, p_index)) {
+
+ int32_t index = p_index;
+
+#ifdef MODULE_FREETYPE_ENABLED
+ if (!fd->msdf && fd->cache[size]->face) {
+ if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ int xshift = (int)(Math::floor(4 * (p_pos.x + 0.125)) - 4 * Math::floor(p_pos.x + 0.125));
+ index = index | (xshift << 27);
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ int xshift = (int)(Math::floor(2 * (p_pos.x + 0.25)) - 2 * Math::floor(p_pos.x + 0.25));
+ index = index | (xshift << 27);
+ }
+ }
+#endif
+
+ if (!_ensure_glyph(fd, size, index)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[p_index];
+ const FontGlyph &gl = fd->cache[size]->glyph_map[index];
if (gl.found) {
ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
@@ -1865,7 +1971,15 @@ void TextServerFallback::font_draw_glyph_outline(RID p_font_rid, RID p_canvas, i
Size2 csize = gl.rect.size * (float)p_size / (float)fd->msdf_source_size;
RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size * 2, fd->msdf_range);
} else {
- Point2 cpos = p_pos.floor();
+ Point2 cpos = p_pos;
+ cpos.y = Math::floor(cpos.y);
+ if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
+ cpos.x = ((int)Math::floor(cpos.x + 0.125));
+ } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
+ cpos.x = ((int)Math::floor(cpos.x + 0.25));
+ } else {
+ cpos.x = Math::floor(cpos.x);
+ }
cpos += gl.rect.position;
Size2 csize = gl.rect.size;
RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
@@ -2660,7 +2774,7 @@ float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width,
end_pos = sd->overrun_trim_data.trim_pos;
justification_width = sd->width_trimmed;
} else {
- return sd->width;
+ return Math::ceil(sd->width);
}
} else {
justification_width = sd->width;
@@ -2720,7 +2834,7 @@ float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width,
sd->width = justification_width;
}
- return justification_width;
+ return Math::ceil(justification_width);
}
float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat32Array &p_tab_stops) {
@@ -3109,6 +3223,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) {
}
if (gl.font_rid.is_valid()) {
+ bool subpos = (font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_ONE_HALF) || (font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_AUTO && gl.font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
if (sd->text[j - sd->start] != 0 && !is_linebreak(sd->text[j - sd->start])) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
gl.advance = Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x);
@@ -3143,6 +3258,9 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) {
}
}
}
+ if (sd->orientation == ORIENTATION_HORIZONTAL && !subpos) {
+ gl.advance = Math::round(gl.advance);
+ }
} else if (sd->preserve_invalid || (sd->preserve_control && is_control(gl.index))) {
// Glyph not found, replace with hex code box.
if (sd->orientation == ORIENTATION_HORIZONTAL) {
@@ -3312,9 +3430,9 @@ Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const {
const_cast(this)->shaped_text_shape(p_shaped);
}
if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) {
- return Size2(sd->width, sd->ascent + sd->descent);
+ return Size2(sd->width, sd->ascent + sd->descent).ceil();
} else {
- return Size2(sd->ascent + sd->descent, sd->width);
+ return Size2(sd->ascent + sd->descent, sd->width).ceil();
}
}
@@ -3348,7 +3466,7 @@ float TextServerFallback::shaped_text_get_width(RID p_shaped) const {
if (!sd->valid) {
const_cast(this)->shaped_text_shape(p_shaped);
}
- return sd->width;
+ return Math::ceil(sd->width);
}
float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const {
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index be944cde581..91afd02ae9d 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -139,6 +139,7 @@ class TextServerFallback : public TextServer {
int fixed_size = 0;
bool force_autohinter = false;
TextServer::Hinting hinting = TextServer::HINTING_LIGHT;
+ TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO;
Dictionary variation_coordinates;
float oversampling = 0.f;
@@ -290,6 +291,9 @@ public:
virtual void font_set_hinting(RID p_font_rid, TextServer::Hinting p_hinting) override;
virtual TextServer::Hinting font_get_hinting(RID p_font_rid) const override;
+ virtual void font_set_subpixel_positioning(RID p_font_rid, SubpixelPositioning p_subpixel) override;
+ virtual SubpixelPositioning font_get_subpixel_positioning(RID p_font_rid) const override;
+
virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) override;
virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const override;
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 8cb8a78e8d8..c4e67c29516 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -84,7 +84,7 @@ void CodeEdit::_notification(int p_what) {
if (line_length_guideline_columns.size() > 0) {
const int xmargin_beg = style_normal->get_margin(SIDE_LEFT) + get_total_gutter_width();
const int xmargin_end = size.width - style_normal->get_margin(SIDE_RIGHT) - (is_drawing_minimap() ? get_minimap_width() : 0);
- const int char_size = Math::round(font->get_char_size('0', 0, font_size).width);
+ const float char_size = font->get_char_size('0', 0, font_size).width;
for (int i = 0; i < line_length_guideline_columns.size(); i++) {
const int xoffset = xmargin_beg + char_size * (int)line_length_guideline_columns[i] - get_h_scroll();
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 3aae3377bc3..0e2cbfea483 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -1606,7 +1606,7 @@ Size2 LineEdit::get_minimum_size() const {
Size2 min_size;
// Minimum size of text.
- int em_space_size = font->get_char_size('M', 0, font_size).x;
+ float em_space_size = font->get_char_size('M', 0, font_size).x;
min_size.width = get_theme_constant(SNAME("minimum_character_width")) * em_space_size;
if (expand_to_text_length) {
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 5295acce8f9..d40f715ae75 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -961,7 +961,7 @@ void TextEdit::_notification(int p_what) {
// Give visual indication of empty selected line.
if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) {
- int char_w = font->get_char_size(' ', 0, font_size).width;
+ float char_w = font->get_char_size(' ', 0, font_size).width;
if (rtl) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), selection_color);
} else {
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 87e9bd023b8..152a1616f88 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -1075,11 +1075,22 @@ void initialize_theme() {
// Allow creating the default theme at a different scale to suit higher/lower base resolutions.
float default_theme_scale = GLOBAL_DEF("gui/theme/default_theme_scale", 1.0);
ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/default_theme_scale", PropertyInfo(Variant::FLOAT, "gui/theme/default_theme_scale", PROPERTY_HINT_RANGE, "0.5,8,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED));
+
String theme_path = GLOBAL_DEF_RST("gui/theme/custom", "");
ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/custom", PropertyInfo(Variant::STRING, "gui/theme/custom", PROPERTY_HINT_FILE, "*.tres,*.res,*.theme", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED));
+
String font_path = GLOBAL_DEF_RST("gui/theme/custom_font", "");
ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/custom_font", PropertyInfo(Variant::STRING, "gui/theme/custom_font", PROPERTY_HINT_FILE, "*.tres,*.res,*.font", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED));
+ bool font_antialiased = (bool)GLOBAL_DEF_RST("gui/theme/default_font_antialiased", true);
+ ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/default_font_antialiased", PropertyInfo(Variant::BOOL, "gui/theme/default_font_antialiased", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED));
+
+ TextServer::Hinting font_hinting = (TextServer::Hinting)(int)GLOBAL_DEF_RST("gui/theme/default_font_hinting", TextServer::HINTING_LIGHT);
+ ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/default_font_hinting", PropertyInfo(Variant::INT, "gui/theme/default_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED));
+
+ TextServer::SubpixelPositioning font_subpixel_positioning = (TextServer::SubpixelPositioning)(int)GLOBAL_DEF_RST("gui/theme/default_font_subpixel_positioning", TextServer::SUBPIXEL_POSITIONING_AUTO);
+ ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/default_font_subpixel_positioning", PropertyInfo(Variant::INT, "gui/theme/default_font_subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED));
+
Ref font;
if (!font_path.is_empty()) {
font = ResourceLoader::load(font_path);
@@ -1090,7 +1101,7 @@ void initialize_theme() {
// Always make the default theme to avoid invalid default font/icon/style in the given theme.
if (RenderingServer::get_singleton()) {
- make_default_theme(default_theme_scale, font);
+ make_default_theme(default_theme_scale, font, font_subpixel_positioning, font_hinting, font_antialiased);
}
if (!theme_path.is_empty()) {
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index c92a46a98cb..b965e5a277c 100644
--- a/scene/resources/default_theme/default_theme.cpp
+++ b/scene/resources/default_theme/default_theme.cpp
@@ -1019,7 +1019,7 @@ void fill_default_theme(Ref &theme, const Ref &default_font, Ref p_font) {
+void make_default_theme(float p_scale, Ref p_font, TextServer::SubpixelPositioning p_subpixel, TextServer::Hinting p_hinting, bool p_aa) {
Ref t;
t.instantiate();
@@ -1041,6 +1041,9 @@ void make_default_theme(float p_scale, Ref p_font) {
Ref dynamic_font_data;
dynamic_font_data.instantiate();
dynamic_font_data->set_data_ptr(_font_OpenSans_SemiBold, _font_OpenSans_SemiBold_size);
+ dynamic_font_data->set_subpixel_positioning(p_subpixel);
+ dynamic_font_data->set_hinting(p_hinting);
+ dynamic_font_data->set_antialiased(p_aa);
dynamic_font->add_data(dynamic_font_data);
default_font = dynamic_font;
diff --git a/scene/resources/default_theme/default_theme.h b/scene/resources/default_theme/default_theme.h
index 3016517824a..28afd5f5e1e 100644
--- a/scene/resources/default_theme/default_theme.h
+++ b/scene/resources/default_theme/default_theme.h
@@ -36,7 +36,7 @@
const int default_font_size = 16;
void fill_default_theme(Ref &theme, const Ref &default_font, Ref &default_icon, Ref &default_style, float p_scale);
-void make_default_theme(float p_scale, Ref p_font);
+void make_default_theme(float p_scale, Ref p_font, TextServer::SubpixelPositioning p_subpixel, TextServer::Hinting p_hinting, bool p_aa);
void clear_default_theme();
#endif
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index b512acdd8a1..5b57e93950f 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -60,6 +60,7 @@ _FORCE_INLINE_ void FontData::_ensure_rid(int p_cache_index) const {
TS->font_set_fixed_size(cache[p_cache_index], fixed_size);
TS->font_set_force_autohinter(cache[p_cache_index], force_autohinter);
TS->font_set_hinting(cache[p_cache_index], hinting);
+ TS->font_set_subpixel_positioning(cache[p_cache_index], subpixel_positioning);
TS->font_set_oversampling(cache[p_cache_index], oversampling);
}
}
@@ -101,6 +102,9 @@ void FontData::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_hinting", "hinting"), &FontData::set_hinting);
ClassDB::bind_method(D_METHOD("get_hinting"), &FontData::get_hinting);
+ ClassDB::bind_method(D_METHOD("set_subpixel_positioning", "subpixel_positioning"), &FontData::set_subpixel_positioning);
+ ClassDB::bind_method(D_METHOD("get_subpixel_positioning"), &FontData::get_subpixel_positioning);
+
ClassDB::bind_method(D_METHOD("set_oversampling", "oversampling"), &FontData::set_oversampling);
ClassDB::bind_method(D_METHOD("get_oversampling"), &FontData::get_oversampling);
@@ -204,6 +208,7 @@ void FontData::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "font_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_name", "get_font_name");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "style_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_style_name", "get_font_style_name");
ADD_PROPERTY(PropertyInfo(Variant::INT, "font_style", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_style", "get_font_style");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel", PROPERTY_USAGE_STORAGE), "set_subpixel_positioning", "get_subpixel_positioning");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_multichannel_signed_distance_field", "is_multichannel_signed_distance_field");
ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_pixel_range", "get_msdf_pixel_range");
ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_size", "get_msdf_size");
@@ -430,6 +435,7 @@ void FontData::reset_state() {
msdf = false;
force_autohinter = false;
hinting = TextServer::HINTING_LIGHT;
+ subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_DISABLED;
msdf_pixel_range = 14;
msdf_size = 128;
fixed_size = 0;
@@ -1364,6 +1370,21 @@ TextServer::Hinting FontData::get_hinting() const {
return hinting;
}
+void FontData::set_subpixel_positioning(TextServer::SubpixelPositioning p_subpixel) {
+ if (subpixel_positioning != p_subpixel) {
+ subpixel_positioning = p_subpixel;
+ for (int i = 0; i < cache.size(); i++) {
+ _ensure_rid(i);
+ TS->font_set_subpixel_positioning(cache[i], subpixel_positioning);
+ }
+ emit_changed();
+ }
+}
+
+TextServer::SubpixelPositioning FontData::get_subpixel_positioning() const {
+ return subpixel_positioning;
+}
+
void FontData::set_oversampling(real_t p_oversampling) {
if (oversampling != p_oversampling) {
oversampling = p_oversampling;
diff --git a/scene/resources/font.h b/scene/resources/font.h
index 93351a34935..aaf0a7fe7ba 100644
--- a/scene/resources/font.h
+++ b/scene/resources/font.h
@@ -55,6 +55,7 @@ class FontData : public Resource {
int fixed_size = 0;
bool force_autohinter = false;
TextServer::Hinting hinting = TextServer::HINTING_LIGHT;
+ TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO;
real_t oversampling = 0.f;
// Cache.
@@ -118,6 +119,9 @@ public:
virtual void set_hinting(TextServer::Hinting p_hinting);
virtual TextServer::Hinting get_hinting() const;
+ virtual void set_subpixel_positioning(TextServer::SubpixelPositioning p_subpixel);
+ virtual TextServer::SubpixelPositioning get_subpixel_positioning() const;
+
virtual void set_oversampling(real_t p_oversampling);
virtual real_t get_oversampling() const;
diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp
index 3c5faa4ef78..5792572dc1e 100644
--- a/servers/text/text_server_extension.cpp
+++ b/servers/text/text_server_extension.cpp
@@ -85,6 +85,9 @@ void TextServerExtension::_bind_methods() {
GDVIRTUAL_BIND(_font_set_hinting, "font_rid", "hinting");
GDVIRTUAL_BIND(_font_get_hinting, "font_rid");
+ GDVIRTUAL_BIND(_font_set_subpixel_positioning, "font_rid", "subpixel_positioning");
+ GDVIRTUAL_BIND(_font_get_subpixel_positioning, "font_rid");
+
GDVIRTUAL_BIND(_font_set_variation_coordinates, "font_rid", "variation_coordinates");
GDVIRTUAL_BIND(_font_get_variation_coordinates, "font_rid");
@@ -512,6 +515,18 @@ TextServer::Hinting TextServerExtension::font_get_hinting(RID p_font_rid) const
return TextServer::Hinting::HINTING_NONE;
}
+void TextServerExtension::font_set_subpixel_positioning(RID p_font_rid, TextServer::SubpixelPositioning p_subpixel) {
+ GDVIRTUAL_CALL(_font_set_subpixel_positioning, p_font_rid, p_subpixel);
+}
+
+TextServer::SubpixelPositioning TextServerExtension::font_get_subpixel_positioning(RID p_font_rid) const {
+ TextServer::SubpixelPositioning ret;
+ if (GDVIRTUAL_CALL(_font_get_subpixel_positioning, p_font_rid, ret)) {
+ return (TextServer::SubpixelPositioning)ret;
+ }
+ return TextServer::SubpixelPositioning::SUBPIXEL_POSITIONING_DISABLED;
+}
+
void TextServerExtension::font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) {
GDVIRTUAL_CALL(_font_set_variation_coordinates, p_font_rid, p_variation_coordinates);
}
diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h
index 9e7f666be18..d185e448062 100644
--- a/servers/text/text_server_extension.h
+++ b/servers/text/text_server_extension.h
@@ -134,6 +134,11 @@ public:
GDVIRTUAL2(_font_set_hinting, RID, Hinting);
GDVIRTUAL1RC(Hinting, _font_get_hinting, RID);
+ virtual void font_set_subpixel_positioning(RID p_font_rid, SubpixelPositioning p_subpixel) override;
+ virtual SubpixelPositioning font_get_subpixel_positioning(RID p_font_rid) const override;
+ GDVIRTUAL2(_font_set_subpixel_positioning, RID, SubpixelPositioning);
+ GDVIRTUAL1RC(SubpixelPositioning, _font_get_subpixel_positioning, RID);
+
virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) override;
virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const override;
GDVIRTUAL2(_font_set_variation_coordinates, RID, Dictionary);
diff --git a/servers/text_server.cpp b/servers/text_server.cpp
index b7cd39b9b2c..37cc6599b16 100644
--- a/servers/text_server.cpp
+++ b/servers/text_server.cpp
@@ -235,9 +235,12 @@ void TextServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("font_set_force_autohinter", "font_rid", "force_autohinter"), &TextServer::font_set_force_autohinter);
ClassDB::bind_method(D_METHOD("font_is_force_autohinter", "font_rid"), &TextServer::font_is_force_autohinter);
- ClassDB::bind_method(D_METHOD("font_set_hinting", "font_rid", "_hinting"), &TextServer::font_set_hinting);
+ ClassDB::bind_method(D_METHOD("font_set_hinting", "font_rid", "hinting"), &TextServer::font_set_hinting);
ClassDB::bind_method(D_METHOD("font_get_hinting", "font_rid"), &TextServer::font_get_hinting);
+ ClassDB::bind_method(D_METHOD("font_set_subpixel_positioning", "font_rid", "subpixel_positioning"), &TextServer::font_set_subpixel_positioning);
+ ClassDB::bind_method(D_METHOD("font_get_subpixel_positioning", "font_rid"), &TextServer::font_get_subpixel_positioning);
+
ClassDB::bind_method(D_METHOD("font_set_variation_coordinates", "font_rid", "variation_coordinates"), &TextServer::font_set_variation_coordinates);
ClassDB::bind_method(D_METHOD("font_get_variation_coordinates", "font_rid"), &TextServer::font_get_variation_coordinates);
@@ -479,6 +482,12 @@ void TextServer::_bind_methods() {
BIND_ENUM_CONSTANT(HINTING_LIGHT);
BIND_ENUM_CONSTANT(HINTING_NORMAL);
+ /* SubpixelPositioning */
+ BIND_ENUM_CONSTANT(SUBPIXEL_POSITIONING_DISABLED);
+ BIND_ENUM_CONSTANT(SUBPIXEL_POSITIONING_AUTO);
+ BIND_ENUM_CONSTANT(SUBPIXEL_POSITIONING_ONE_HALF);
+ BIND_ENUM_CONSTANT(SUBPIXEL_POSITIONING_ONE_QUARTER);
+
/* Feature */
BIND_ENUM_CONSTANT(FEATURE_BIDI_LAYOUT);
BIND_ENUM_CONSTANT(FEATURE_VERTICAL_LAYOUT);
diff --git a/servers/text_server.h b/servers/text_server.h
index 629a633b9f0..38ad4964902 100644
--- a/servers/text_server.h
+++ b/servers/text_server.h
@@ -101,6 +101,16 @@ public:
HINTING_NORMAL
};
+ enum SubpixelPositioning {
+ SUBPIXEL_POSITIONING_DISABLED,
+ SUBPIXEL_POSITIONING_AUTO,
+ SUBPIXEL_POSITIONING_ONE_HALF,
+ SUBPIXEL_POSITIONING_ONE_QUARTER,
+ };
+
+ const int SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE = 20;
+ const int SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE = 16;
+
enum Feature {
FEATURE_BIDI_LAYOUT = 1 << 0,
FEATURE_VERTICAL_LAYOUT = 1 << 1,
@@ -248,6 +258,9 @@ public:
virtual void font_set_hinting(RID p_font_rid, Hinting p_hinting) = 0;
virtual Hinting font_get_hinting(RID p_font_rid) const = 0;
+ virtual void font_set_subpixel_positioning(RID p_font_rid, SubpixelPositioning p_subpixel) = 0;
+ virtual SubpixelPositioning font_get_subpixel_positioning(RID p_font_rid) const = 0;
+
virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) = 0;
virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const = 0;
@@ -551,6 +564,7 @@ VARIANT_ENUM_CAST(TextServer::LineBreakFlag);
VARIANT_ENUM_CAST(TextServer::TextOverrunFlag);
VARIANT_ENUM_CAST(TextServer::GraphemeFlag);
VARIANT_ENUM_CAST(TextServer::Hinting);
+VARIANT_ENUM_CAST(TextServer::SubpixelPositioning);
VARIANT_ENUM_CAST(TextServer::Feature);
VARIANT_ENUM_CAST(TextServer::ContourPointTag);
VARIANT_ENUM_CAST(TextServer::SpacingType);
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 34c87d64b73..830731abcdf 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -206,7 +206,7 @@ struct GodotTestCaseListener : public doctest::IReporter {
memnew(InputMap);
InputMap::get_singleton()->load_default();
- make_default_theme(1.0, Ref());
+ make_default_theme(1.0, Ref(), TextServer::SUBPIXEL_POSITIONING_AUTO, TextServer::HINTING_LIGHT, true);
memnew(SceneTree);
SceneTree::get_singleton()->initialize();