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();