diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 56c41aa7494..aa862168b19 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -115,7 +115,7 @@ jobs: sudo apt-get install build-essential pkg-config libx11-dev libxcursor-dev \ libxinerama-dev libgl1-mesa-dev libglu-dev libasound2-dev libpulse-dev \ libdbus-1-dev libudev-dev libxi-dev libxrandr-dev yasm xvfb wget unzip \ - llvm libspeechd-dev speech-dispatcher + llvm libspeechd-dev speech-dispatcher fontconfig libfontconfig-dev - name: Setup Godot build cache uses: ./.github/actions/godot-cache diff --git a/core/core_bind.cpp b/core/core_bind.cpp index cb57cd70573..8e776d6f051 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -231,6 +231,14 @@ void OS::crash(const String &p_message) { CRASH_NOW_MSG(p_message); } +Vector OS::get_system_fonts() const { + return ::OS::get_singleton()->get_system_fonts(); +} + +String OS::get_system_font_path(const String &p_font_name, bool p_bold, bool p_italic) const { + return ::OS::get_singleton()->get_system_font_path(p_font_name, p_bold, p_italic); +} + String OS::get_executable_path() const { return ::OS::get_singleton()->get_executable_path(); } @@ -589,6 +597,8 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_processor_count"), &OS::get_processor_count); ClassDB::bind_method(D_METHOD("get_processor_name"), &OS::get_processor_name); + ClassDB::bind_method(D_METHOD("get_system_fonts"), &OS::get_system_fonts); + ClassDB::bind_method(D_METHOD("get_system_font_path", "font_name", "bold", "italic"), &OS::get_system_font_path, DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path); ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false)); diff --git a/core/core_bind.h b/core/core_bind.h index c116ac4986e..e222a220f04 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -170,6 +170,8 @@ public: void alert(const String &p_alert, const String &p_title = "ALERT!"); void crash(const String &p_message); + Vector get_system_fonts() const; + String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const; String get_executable_path() const; int execute(const String &p_path, const Vector &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false); int create_process(const String &p_path, const Vector &p_arguments, bool p_open_console = false); diff --git a/core/os/os.h b/core/os/os.h index 0428f6df2a8..b9f73289296 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -142,6 +142,8 @@ public: virtual void set_low_processor_usage_mode_sleep_usec(int p_usec); virtual int get_low_processor_usage_mode_sleep_usec() const; + virtual Vector get_system_fonts() const { return Vector(); }; + virtual String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const { return String(); }; virtual String get_executable_path() const; virtual Error execute(const String &p_path, const List &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0; virtual Error create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 0; diff --git a/doc/classes/FontFile.xml b/doc/classes/FontFile.xml index aaf871d55a0..dc2cbdde63f 100644 --- a/doc/classes/FontFile.xml +++ b/doc/classes/FontFile.xml @@ -1,7 +1,7 @@ - FontFile source data and prerendered glyph cache, imported from dynamic or bitmap font. + Font source data and prerendered glyph cache, imported from dynamic or bitmap font. [FontFile] contains a set of glyphs to represent Unicode characters imported from a font file, as well as a cache of rasterized glyphs, and a set of fallback [Font]s to use. diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index aaf08dec2fd..5473347cb12 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -385,6 +385,23 @@ [b]Note:[/b] Shared storage is implemented on Android and allows to differentiate between app specific and shared directories. Shared directories have additional restrictions on Android. + + + + + + + Returns path to the system font file with [code]font_name[/code] and style. Return empty string if no matching fonts found. + [b]Note:[/b] This method is implemented on iOS, Linux, macOS and Windows. + + + + + + Returns list of font family names available. + [b]Note:[/b] This method is implemented on iOS, Linux, macOS and Windows. + + diff --git a/doc/classes/SystemFont.xml b/doc/classes/SystemFont.xml new file mode 100644 index 00000000000..b1b78f1705a --- /dev/null +++ b/doc/classes/SystemFont.xml @@ -0,0 +1,44 @@ + + + + Font loaded from a system font. + [b]Note:[/b] This class is implemented on iOS, Linux, macOS and Windows, on other platforms it will fallback to default theme font. + + + [SystemFont] loads a font from a system font with the first matching name from [member font_names]. + It will attempt to match font style, but it's not guaranteed. + The returned font might be part of a font collection or be a variable font with OpenType "weight" and/or "italic" features set. + You can create [FontVariation] of the system font for fine control over its features. + + + + + + If set to [code]true[/code], font 8-bit anitialiased glyph rendering is supported and enabled. + + + Array of fallback [Font]s. + + + Array of font family names to search, first matching font found is used. + + + Font style flags, see [enum TextServer.FontStyle]. + + + If set to [code]true[/code], auto-hinting is supported and preferred over font built-in hinting. + + + If set to [code]true[/code], generate mipmaps for the font textures. + + + Font hinting mode. + + + Font oversampling factor, if set to [code]0.0[/code] global oversampling factor is used instead. + + + 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/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h index 44623149d03..82aeebe14a9 100644 --- a/editor/editor_properties_array_dict.h +++ b/editor/editor_properties_array_dict.h @@ -81,10 +81,7 @@ class EditorPropertyArray : public EditorProperty { GDCLASS(EditorPropertyArray, EditorProperty); PopupMenu *change_type = nullptr; - bool updating = false; - bool dropping = false; - Ref object; int page_length = 20; int page_index = 0; int changing_type_index; @@ -106,29 +103,35 @@ class EditorPropertyArray : public EditorProperty { Button *reorder_selected_button = nullptr; void _page_changed(int p_page); - void _length_changed(double p_page); - void _add_element(); - void _edit_pressed(); - void _property_changed(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false); - void _change_type(Object *p_button, int p_index); - void _change_type_menu(int p_index); - - void _object_id_selected(const StringName &p_property, ObjectID p_id); - void _remove_pressed(int p_index); - - void _button_draw(); - bool _is_drop_valid(const Dictionary &p_drag_data) const; - bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; - void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); void _reorder_button_gui_input(const Ref &p_event); void _reorder_button_down(int p_index); void _reorder_button_up(); protected: + Ref object; + + bool updating = false; + bool dropping = false; + static void _bind_methods(); void _notification(int p_what); + virtual void _add_element(); + virtual void _length_changed(double p_page); + virtual void _edit_pressed(); + virtual void _property_changed(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false); + virtual void _change_type(Object *p_button, int p_index); + virtual void _change_type_menu(int p_index); + + virtual void _object_id_selected(const StringName &p_property, ObjectID p_id); + virtual void _remove_pressed(int p_index); + + virtual void _button_draw(); + virtual bool _is_drop_valid(const Dictionary &p_drag_data) const; + virtual bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + virtual void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + public: void setup(Variant::Type p_array_type, const String &p_hint_string = ""); virtual void update_property() override; diff --git a/editor/icons/FontFile.svg b/editor/icons/FontFile.svg index 4b94fd0d743..c0142997839 100644 --- a/editor/icons/FontFile.svg +++ b/editor/icons/FontFile.svg @@ -1 +1 @@ - + diff --git a/editor/icons/FontVariation.svg b/editor/icons/FontVariation.svg index eaad049fcee..39917bcba9f 100644 --- a/editor/icons/FontVariation.svg +++ b/editor/icons/FontVariation.svg @@ -1 +1 @@ - + diff --git a/editor/icons/SystemFont.svg b/editor/icons/SystemFont.svg new file mode 100644 index 00000000000..a6f62d56d3f --- /dev/null +++ b/editor/icons/SystemFont.svg @@ -0,0 +1 @@ + diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp index 848fb5887d1..a7b0f8b1484 100644 --- a/editor/plugins/font_config_plugin.cpp +++ b/editor/plugins/font_config_plugin.cpp @@ -895,17 +895,45 @@ void FontPreview::_notification(int p_what) { Ref font = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); Color text_color = get_theme_color(SNAME("font_color"), SNAME("Label")); - font->draw_string(get_canvas_item(), Point2(0, font->get_height(font_size) + 2 * EDSCALE), name, HORIZONTAL_ALIGNMENT_CENTER, get_size().x, font_size, text_color); // Draw font preview. - Vector2 pos = Vector2(0, font->get_height(font_size)) + (get_size() - Vector2(0, font->get_height(font_size)) - line->get_size()) / 2; - line->draw(get_canvas_item(), pos, text_color); + bool prev_ok = true; + if (prev_font.is_valid()) { + if (prev_font->get_font_name().is_empty()) { + prev_ok = false; + } else { + String name; + if (prev_font->get_font_style_name().is_empty()) { + name = prev_font->get_font_name(); + } else { + name = vformat("%s (%s)", prev_font->get_font_name(), prev_font->get_font_style_name()); + } + if (prev_font->is_class("FontVariation")) { + name += " " + TTR(" - Variation"); + } + font->draw_string(get_canvas_item(), Point2(0, font->get_height(font_size) + 2 * EDSCALE), name, HORIZONTAL_ALIGNMENT_CENTER, get_size().x, font_size, text_color); - // Draw font baseline. - Color line_color = text_color; - line_color.a *= 0.6; - draw_line(Vector2(0, pos.y + line->get_line_ascent()), Vector2(pos.x - 5, pos.y + line->get_line_ascent()), line_color); - draw_line(Vector2(pos.x + line->get_size().x + 5, pos.y + line->get_line_ascent()), Vector2(get_size().x, pos.y + line->get_line_ascent()), line_color); + String sample; + static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀"; + for (int i = 0; i < sample_base.length(); i++) { + if (prev_font->has_char(sample_base[i])) { + sample += sample_base[i]; + } + } + if (sample.is_empty()) { + sample = prev_font->get_supported_chars().substr(0, 6); + } + if (sample.is_empty()) { + prev_ok = false; + } else { + prev_font->draw_string(get_canvas_item(), Point2(0, font->get_height(font_size) + prev_font->get_height(50)), sample, HORIZONTAL_ALIGNMENT_CENTER, get_size().x, 50, text_color); + } + } + } + if (!prev_ok) { + text_color.a *= 0.5; + font->draw_string(get_canvas_item(), Point2(0, font->get_height(font_size) + 2 * EDSCALE), TTR("Unable to preview font"), HORIZONTAL_ALIGNMENT_CENTER, get_size().x, font_size, text_color); + } } break; } } @@ -917,30 +945,11 @@ Size2 FontPreview::get_minimum_size() const { } void FontPreview::set_data(const Ref &p_f) { - line->clear(); - if (p_f.is_valid()) { - name = vformat("%s (%s)", p_f->get_font_name(), p_f->get_font_style_name()); - if (p_f->is_class("FontVariation")) { - name += " " + TTR(" - Variation"); - } - String sample; - static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀"; - for (int i = 0; i < sample_base.length(); i++) { - if (p_f->has_char(sample_base[i])) { - sample += sample_base[i]; - } - } - if (sample.is_empty()) { - sample = p_f->get_supported_chars().substr(0, 6); - } - line->add_string(sample, p_f, 50); - } - + prev_font = p_f; update(); } FontPreview::FontPreview() { - line.instantiate(); } /*************************************************************************/ @@ -964,6 +973,71 @@ bool EditorInspectorPluginFontPreview::parse_property(Object *p_object, const Va return false; } +/*************************************************************************/ +/* EditorPropertyFontNamesArray */ +/*************************************************************************/ + +void EditorPropertyFontNamesArray::_add_element() { + Size2 size = get_size(); + menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); + menu->reset_size(); + menu->popup(); +} + +void EditorPropertyFontNamesArray::_add_font(int p_option) { + if (updating) { + return; + } + + Variant array = object->get_array(); + int previous_size = array.call("size"); + + array.call("resize", previous_size + 1); + array.set(previous_size, menu->get_item_text(p_option)); + + emit_changed(get_edited_property(), array, "", false); + object->set_array(array); + update_property(); +} + +EditorPropertyFontNamesArray::EditorPropertyFontNamesArray() { + menu = memnew(PopupMenu); + menu->add_item("Sans-Serif", 0); + menu->add_item("Serif", 1); + menu->add_item("Monospace", 2); + menu->add_item("Fantasy", 3); + menu->add_item("Cursive", 4); + + menu->add_separator(); + + if (OS::get_singleton()) { + Vector fonts = OS::get_singleton()->get_system_fonts(); + for (int i = 0; i < fonts.size(); i++) { + menu->add_item(fonts[i], i + 6); + } + } + add_child(menu); + menu->connect("id_pressed", callable_mp(this, &EditorPropertyFontNamesArray::_add_font)); +} + +/*************************************************************************/ +/* EditorInspectorPluginSystemFont */ +/*************************************************************************/ + +bool EditorInspectorPluginSystemFont::can_handle(Object *p_object) { + return Object::cast_to(p_object) != nullptr; +} + +bool EditorInspectorPluginSystemFont::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { + if (p_path == "font_names") { + EditorPropertyFontNamesArray *editor = memnew(EditorPropertyFontNamesArray); + editor->setup(p_type, p_hint_text); + add_property_editor(p_path, editor); + return true; + } + return false; +} + /*************************************************************************/ /* FontEditorPlugin */ /*************************************************************************/ @@ -973,6 +1047,10 @@ FontEditorPlugin::FontEditorPlugin() { fc_plugin.instantiate(); EditorInspector::add_inspector_plugin(fc_plugin); + Ref fs_plugin; + fs_plugin.instantiate(); + EditorInspector::add_inspector_plugin(fs_plugin); + Ref fp_plugin; fp_plugin.instantiate(); EditorInspector::add_inspector_plugin(fp_plugin); diff --git a/editor/plugins/font_config_plugin.h b/editor/plugins/font_config_plugin.h index 67c16bfe60d..3eaa2fdc176 100644 --- a/editor/plugins/font_config_plugin.h +++ b/editor/plugins/font_config_plugin.h @@ -34,6 +34,7 @@ #include "core/io/marshalls.h" #include "editor/editor_plugin.h" #include "editor/editor_properties.h" +#include "editor/editor_properties_array_dict.h" /*************************************************************************/ @@ -225,8 +226,7 @@ protected: void _notification(int p_what); static void _bind_methods(); - String name; - Ref line; + Ref prev_font; public: virtual Size2 get_minimum_size() const override; @@ -249,6 +249,33 @@ public: /*************************************************************************/ +class EditorPropertyFontNamesArray : public EditorPropertyArray { + GDCLASS(EditorPropertyFontNamesArray, EditorPropertyArray); + + PopupMenu *menu = nullptr; + +protected: + virtual void _add_element() override; + + void _add_font(int p_option); + static void _bind_methods(){}; + +public: + EditorPropertyFontNamesArray(); +}; + +/*************************************************************************/ + +class EditorInspectorPluginSystemFont : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginSystemFont, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; +}; + +/*************************************************************************/ + class FontEditorPlugin : public EditorPlugin { GDCLASS(FontEditorPlugin, EditorPlugin); diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h index cfd17716536..3b88f53b6a1 100644 --- a/platform/ios/os_ios.h +++ b/platform/ios/os_ios.h @@ -92,6 +92,9 @@ public: virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + virtual Vector get_system_fonts() const override; + virtual String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const override; + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override; virtual Error close_dynamic_library(void *p_library_handle) override; virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override; diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm index 880315209ef..b9d186f3552 100644 --- a/platform/ios/os_ios.mm +++ b/platform/ios/os_ios.mm @@ -44,6 +44,7 @@ #import "view_controller.h" #import +#import #import #import #include @@ -302,6 +303,80 @@ String OS_IOS::get_processor_name() const { ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string.")); } +Vector OS_IOS::get_system_fonts() const { + HashSet font_names; + CFArrayRef fonts = CTFontManagerCopyAvailableFontFamilyNames(); + if (fonts) { + for (CFIndex i = 0; i < CFArrayGetCount(fonts); i++) { + CFStringRef cf_name = (CFStringRef)CFArrayGetValueAtIndex(fonts, i); + if (cf_name && (CFStringGetLength(cf_name) > 0) && (CFStringCompare(cf_name, CFSTR("LastResort"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) && (CFStringGetCharacterAtIndex(cf_name, 0) != '.')) { + NSString *ns_name = (__bridge NSString *)cf_name; + font_names.insert(String::utf8([ns_name UTF8String])); + } + } + CFRelease(fonts); + } + + Vector ret; + for (const String &E : font_names) { + ret.push_back(E); + } + return ret; +} + +String OS_IOS::get_system_font_path(const String &p_font_name, bool p_bold, bool p_italic) const { + String ret; + + String font_name = p_font_name; + if (font_name.to_lower() == "sans-serif") { + font_name = "Helvetica"; + } else if (font_name.to_lower() == "serif") { + font_name = "Times"; + } else if (font_name.to_lower() == "monospace") { + font_name = "Courier"; + } else if (font_name.to_lower() == "fantasy") { + font_name = "Papyrus"; + } else if (font_name.to_lower() == "cursive") { + font_name = "Apple Chancery"; + }; + + CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8); + + CTFontSymbolicTraits traits = 0; + if (p_bold) { + traits |= kCTFontBoldTrait; + } + if (p_italic) { + traits |= kCTFontItalicTrait; + } + + CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits); + CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits); + + CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name); + CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict); + + CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes); + if (font) { + CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(font, kCTFontURLAttribute); + if (url) { + NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]]; + ret = String::utf8([font_path UTF8String]); + CFRelease(url); + } + CFRelease(font); + } + + CFRelease(attributes); + CFRelease(traits_dict); + CFRelease(sym_traits); + CFRelease(name); + + return ret; +} + void OS_IOS::vibrate_handheld(int p_duration_ms) { if (ios->supports_haptic_engine()) { ios->vibrate_haptic_engine((float)p_duration_ms / 1000.f); diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 065250c40ea..b9cbf9b97c8 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -298,6 +298,12 @@ def configure(env): ## Flags + if os.system("pkg-config --exists fontconfig") == 0: # 0 means found + env.Append(CPPDEFINES=["FONTCONFIG_ENABLED"]) + env.ParseConfig("pkg-config fontconfig --cflags --libs") + else: + print("Warning: fontconfig libraries not found. Disabling the system fonts support.") + if os.system("pkg-config --exists alsa") == 0: # 0 means found env["alsa"] = True env.Append(CPPDEFINES=["ALSA_ENABLED", "ALSAMIDI_ENABLED"]) diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index b73d4dc626a..bf4634a4fd0 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -52,6 +52,10 @@ #include #include +#ifdef FONTCONFIG_ENABLED +#include +#endif + void OS_LinuxBSD::alert(const String &p_alert, const String &p_title) { const char *message_programs[] = { "zenity", "kdialog", "Xdialog", "xmessage" }; @@ -327,6 +331,91 @@ uint64_t OS_LinuxBSD::get_embedded_pck_offset() const { return off; } +Vector OS_LinuxBSD::get_system_fonts() const { +#ifdef FONTCONFIG_ENABLED + HashSet font_names; + Vector ret; + + FcConfig *config = FcInitLoadConfigAndFonts(); + ERR_FAIL_COND_V(!config, ret); + + FcObjectSet *object_set = FcObjectSetBuild(FC_FAMILY, nullptr); + ERR_FAIL_COND_V(!object_set, ret); + + static const char *allowed_formats[] = { "TrueType", "CFF" }; + for (size_t i = 0; i < sizeof(allowed_formats) / sizeof(const char *); i++) { + FcPattern *pattern = FcPatternCreate(); + ERR_CONTINUE(!pattern); + + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); + FcPatternAddString(pattern, FC_FONTFORMAT, reinterpret_cast(allowed_formats[i])); + + FcFontSet *font_set = FcFontList(config, pattern, object_set); + if (font_set) { + for (int j = 0; j < font_set->nfont; j++) { + char *family_name = nullptr; + if (FcPatternGetString(font_set->fonts[j], FC_FAMILY, 0, reinterpret_cast(&family_name)) == FcResultMatch) { + if (family_name) { + font_names.insert(String::utf8(family_name)); + } + } + } + FcFontSetDestroy(font_set); + } + FcPatternDestroy(pattern); + } + FcObjectSetDestroy(object_set); + + for (const String &E : font_names) { + ret.push_back(E); + } + return ret; +#else + ERR_FAIL_COND_V_MSG(Vector(), "Godot was compiled without fontconfig, system font support is disabled.") +#endif +} + +String OS_LinuxBSD::get_system_font_path(const String &p_font_name, bool p_bold, bool p_italic) const { +#ifdef FONTCONFIG_ENABLED + String ret; + + FcConfig *config = FcInitLoadConfigAndFonts(); + ERR_FAIL_COND_V(!config, ret); + + FcObjectSet *object_set = FcObjectSetBuild(FC_FAMILY, FC_FILE, nullptr); + ERR_FAIL_COND_V(!object_set, ret); + + FcPattern *pattern = FcPatternCreate(); + if (pattern) { + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); + FcPatternAddString(pattern, FC_FAMILY, reinterpret_cast(p_font_name.utf8().get_data())); + FcPatternAddInteger(pattern, FC_WEIGHT, p_bold ? FC_WEIGHT_BOLD : FC_WEIGHT_NORMAL); + FcPatternAddInteger(pattern, FC_SLANT, p_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN); + + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + FcResult result; + FcPattern *match = FcFontMatch(0, pattern, &result); + if (match) { + char *file_name = nullptr; + if (FcPatternGetString(match, FC_FILE, 0, reinterpret_cast(&file_name)) == FcResultMatch) { + if (file_name) { + ret = String::utf8(file_name); + } + } + + FcPatternDestroy(match); + } + FcPatternDestroy(pattern); + } + FcObjectSetDestroy(object_set); +#else + ERR_FAIL_COND_V_MSG(Vector(), "Godot was compiled without fontconfig, system font support is disabled.") +#endif + return ret; +} + String OS_LinuxBSD::get_config_path() const { if (has_environment("XDG_CONFIG_HOME")) { if (get_environment("XDG_CONFIG_HOME").is_absolute_path()) { diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index 13c07842fb9..095bcb64273 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -80,6 +80,9 @@ public: virtual uint64_t get_embedded_pck_offset() const override; + virtual Vector get_system_fonts() const override; + virtual String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const override; + virtual String get_config_path() const override; virtual String get_data_path() const override; virtual String get_cache_path() const override; diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index a6c23ab71eb..a1eb0f7f696 100644 --- a/platform/macos/os_macos.h +++ b/platform/macos/os_macos.h @@ -97,6 +97,8 @@ public: virtual String get_locale() const override; + virtual Vector get_system_fonts() const override; + virtual String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const override; virtual String get_executable_path() const override; virtual Error create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error create_instance(const List &p_arguments, ProcessID *r_child_id = nullptr) override; diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 2c6cd7de0bc..cc550043de8 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -303,6 +303,80 @@ String OS_MacOS::get_locale() const { return String([locale_code UTF8String]).replace("-", "_"); } +Vector OS_MacOS::get_system_fonts() const { + HashSet font_names; + CFArrayRef fonts = CTFontManagerCopyAvailableFontFamilyNames(); + if (fonts) { + for (CFIndex i = 0; i < CFArrayGetCount(fonts); i++) { + CFStringRef cf_name = (CFStringRef)CFArrayGetValueAtIndex(fonts, i); + if (cf_name && (CFStringGetLength(cf_name) > 0) && (CFStringCompare(cf_name, CFSTR("LastResort"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) && (CFStringGetCharacterAtIndex(cf_name, 0) != '.')) { + NSString *ns_name = (__bridge NSString *)cf_name; + font_names.insert(String::utf8([ns_name UTF8String])); + } + } + CFRelease(fonts); + } + + Vector ret; + for (const String &E : font_names) { + ret.push_back(E); + } + return ret; +} + +String OS_MacOS::get_system_font_path(const String &p_font_name, bool p_bold, bool p_italic) const { + String ret; + + String font_name = p_font_name; + if (font_name.to_lower() == "sans-serif") { + font_name = "Helvetica"; + } else if (font_name.to_lower() == "serif") { + font_name = "Times"; + } else if (font_name.to_lower() == "monospace") { + font_name = "Courier"; + } else if (font_name.to_lower() == "fantasy") { + font_name = "Papyrus"; + } else if (font_name.to_lower() == "cursive") { + font_name = "Apple Chancery"; + }; + + CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8); + + CTFontSymbolicTraits traits = 0; + if (p_bold) { + traits |= kCTFontBoldTrait; + } + if (p_italic) { + traits |= kCTFontItalicTrait; + } + + CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits); + CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits); + + CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name); + CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict); + + CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes); + if (font) { + CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(font, kCTFontURLAttribute); + if (url) { + NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]]; + ret = String::utf8([font_path UTF8String]); + CFRelease(url); + } + CFRelease(font); + } + + CFRelease(attributes); + CFRelease(traits_dict); + CFRelease(sym_traits); + CFRelease(name); + + return ret; +} + String OS_MacOS::get_executable_path() const { char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; int pid = getpid(); diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 6a7caf4656a..dd2df1f0044 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -270,6 +270,7 @@ def configure_msvc(env, manual_msvc_config): "bcrypt", "Avrt", "dwmapi", + "dwrite", ] if env["vulkan"]: @@ -441,6 +442,7 @@ def configure_mingw(env): "avrt", "uuid", "dwmapi", + "dwrite", ] ) diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index b5423e62bf3..ad4be950cce 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -621,6 +622,135 @@ Error OS_Windows::set_cwd(const String &p_cwd) { return OK; } +Vector OS_Windows::get_system_fonts() const { + Vector ret; + HashSet font_names; + + ComAutoreleaseRef dwrite_factory; + HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&dwrite_factory.reference)); + ERR_FAIL_COND_V(FAILED(hr) || dwrite_factory.is_null(), ret); + + ComAutoreleaseRef font_collection; + hr = dwrite_factory->GetSystemFontCollection(&font_collection.reference, false); + ERR_FAIL_COND_V(FAILED(hr) || font_collection.is_null(), ret); + + UINT32 family_count = font_collection->GetFontFamilyCount(); + for (UINT32 i = 0; i < family_count; i++) { + ComAutoreleaseRef family; + hr = font_collection->GetFontFamily(i, &family.reference); + ERR_CONTINUE(FAILED(hr) || family.is_null()); + + ComAutoreleaseRef family_names; + hr = family->GetFamilyNames(&family_names.reference); + ERR_CONTINUE(FAILED(hr) || family_names.is_null()); + + UINT32 index = 0; + BOOL exists = false; + UINT32 length = 0; + Char16String name; + + hr = family_names->FindLocaleName(L"en-us", &index, &exists); + ERR_CONTINUE(FAILED(hr)); + + hr = family_names->GetStringLength(index, &length); + ERR_CONTINUE(FAILED(hr)); + + name.resize(length + 1); + hr = family_names->GetString(index, (WCHAR *)name.ptrw(), length + 1); + ERR_CONTINUE(FAILED(hr)); + + font_names.insert(String::utf16(name.ptr(), length)); + } + + for (const String &E : font_names) { + ret.push_back(E); + } + return ret; +} + +String OS_Windows::get_system_font_path(const String &p_font_name, bool p_bold, bool p_italic) const { + String font_name = p_font_name; + if (font_name.to_lower() == "sans-serif") { + font_name = "Arial"; + } else if (font_name.to_lower() == "serif") { + font_name = "Times New Roman"; + } else if (font_name.to_lower() == "monospace") { + font_name = "Courier New"; + } else if (font_name.to_lower() == "cursive") { + font_name = "Comic Sans MS"; + } else if (font_name.to_lower() == "fantasy") { + font_name = "Gabriola"; + } + + ComAutoreleaseRef dwrite_factory; + HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&dwrite_factory.reference)); + ERR_FAIL_COND_V(FAILED(hr) || dwrite_factory.is_null(), String()); + + ComAutoreleaseRef font_collection; + hr = dwrite_factory->GetSystemFontCollection(&font_collection.reference, false); + ERR_FAIL_COND_V(FAILED(hr) || font_collection.is_null(), String()); + + UINT32 index = 0; + BOOL exists = false; + font_collection->FindFamilyName((const WCHAR *)font_name.utf16().get_data(), &index, &exists); + if (FAILED(hr)) { + return String(); + } + + ComAutoreleaseRef family; + hr = font_collection->GetFontFamily(index, &family.reference); + if (FAILED(hr) || family.is_null()) { + return String(); + } + + ComAutoreleaseRef dwrite_font; + hr = family->GetFirstMatchingFont(p_bold ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, p_italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL, &dwrite_font.reference); + if (FAILED(hr) || dwrite_font.is_null()) { + return String(); + } + + ComAutoreleaseRef dwrite_face; + hr = dwrite_font->CreateFontFace(&dwrite_face.reference); + if (FAILED(hr) || dwrite_face.is_null()) { + return String(); + } + + UINT32 number_of_files = 0; + hr = dwrite_face->GetFiles(&number_of_files, nullptr); + if (FAILED(hr)) { + return String(); + } + Vector> files; + files.resize(number_of_files); + hr = dwrite_face->GetFiles(&number_of_files, (IDWriteFontFile **)files.ptrw()); + if (FAILED(hr)) { + return String(); + } + + for (UINT32 i = 0; i < number_of_files; i++) { + void const *reference_key = nullptr; + UINT32 reference_key_size = 0; + ComAutoreleaseRef loader; + + hr = files.write[i]->GetLoader((IDWriteFontFileLoader **)&loader.reference); + if (FAILED(hr) || loader.is_null()) { + continue; + } + hr = files.write[i]->GetReferenceKey(&reference_key, &reference_key_size); + if (FAILED(hr)) { + continue; + } + + WCHAR file_path[MAX_PATH]; + hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], MAX_PATH); + if (FAILED(hr)) { + continue; + } + return String::utf16((const char16_t *)&file_path[0]); + } + return String(); +} + String OS_Windows::get_executable_path() const { WCHAR bufname[4096]; GetModuleFileNameW(nullptr, bufname, 4096); diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 7d2d4ae705f..80fc860738c 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -62,6 +62,26 @@ #define WINDOWS_DEBUG_OUTPUT_ENABLED #endif +template +class ComAutoreleaseRef { +public: + T *reference = nullptr; + + _FORCE_INLINE_ T *operator->() { return reference; } + _FORCE_INLINE_ const T *operator->() const { return reference; } + _FORCE_INLINE_ T *operator*() { return reference; } + _FORCE_INLINE_ const T *operator*() const { return reference; } + _FORCE_INLINE_ bool is_valid() const { return reference != nullptr; } + _FORCE_INLINE_ bool is_null() const { return reference == nullptr; } + ComAutoreleaseRef() {} + ~ComAutoreleaseRef() { + if (reference != nullptr) { + reference->Release(); + reference = nullptr; + } + } +}; + class JoypadWindows; class OS_Windows : public OS { #ifdef STDOUT_FILE @@ -147,6 +167,9 @@ public: virtual String get_environment(const String &p_var) const override; virtual bool set_environment(const String &p_var, const String &p_value) const override; + virtual Vector get_system_fonts() const override; + virtual String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const override; + virtual String get_executable_path() const override; virtual String get_locale() const override; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 06553cd0f6f..630a3316d6f 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -443,8 +443,10 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_THEME_CHANGED: { - _update_caches(); - _update_wrap_at_column(true); + if (is_inside_tree()) { + _update_caches(); + _update_wrap_at_column(true); + } } break; case NOTIFICATION_WM_WINDOW_FOCUS_IN: { diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 09a283ea53f..ef5ac36114e 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -870,6 +870,7 @@ void register_scene_types() { GDREGISTER_ABSTRACT_CLASS(Font); GDREGISTER_CLASS(FontFile); GDREGISTER_CLASS(FontVariation); + GDREGISTER_CLASS(SystemFont); GDREGISTER_CLASS(Curve); diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index dd728b3af11..c469946b45b 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -2693,3 +2693,374 @@ FontVariation::FontVariation() { FontVariation::~FontVariation() { reset_state(); } + +/*************************************************************************/ +/* SystemFont */ +/*************************************************************************/ + +void SystemFont::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_antialiased", "antialiased"), &SystemFont::set_antialiased); + ClassDB::bind_method(D_METHOD("is_antialiased"), &SystemFont::is_antialiased); + + ClassDB::bind_method(D_METHOD("set_generate_mipmaps", "generate_mipmaps"), &SystemFont::set_generate_mipmaps); + ClassDB::bind_method(D_METHOD("get_generate_mipmaps"), &SystemFont::get_generate_mipmaps); + + ClassDB::bind_method(D_METHOD("set_force_autohinter", "force_autohinter"), &SystemFont::set_force_autohinter); + ClassDB::bind_method(D_METHOD("is_force_autohinter"), &SystemFont::is_force_autohinter); + + ClassDB::bind_method(D_METHOD("set_hinting", "hinting"), &SystemFont::set_hinting); + ClassDB::bind_method(D_METHOD("get_hinting"), &SystemFont::get_hinting); + + ClassDB::bind_method(D_METHOD("set_subpixel_positioning", "subpixel_positioning"), &SystemFont::set_subpixel_positioning); + ClassDB::bind_method(D_METHOD("get_subpixel_positioning"), &SystemFont::get_subpixel_positioning); + + ClassDB::bind_method(D_METHOD("set_oversampling", "oversampling"), &SystemFont::set_oversampling); + ClassDB::bind_method(D_METHOD("get_oversampling"), &SystemFont::get_oversampling); + + ClassDB::bind_method(D_METHOD("get_font_names"), &SystemFont::get_font_names); + ClassDB::bind_method(D_METHOD("set_font_names", "names"), &SystemFont::set_font_names); + + ClassDB::bind_method(D_METHOD("set_font_style", "style"), &SystemFont::set_font_style); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "font_names"), "set_font_names", "get_font_names"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_style", PROPERTY_HINT_FLAGS, "Bold,Italic"), "set_font_style", "get_font_style"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "antialiased"), "set_antialiased", "is_antialiased"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_mipmaps"), "set_generate_mipmaps", "get_generate_mipmaps"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_autohinter"), "set_force_autohinter", "is_force_autohinter"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), "set_hinting", "get_hinting"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel"), "set_subpixel_positioning", "get_subpixel_positioning"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), "set_oversampling", "get_oversampling"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "fallbacks", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Font")), "set_fallbacks", "get_fallbacks"); +} + +void SystemFont::_update_rids() const { + Ref f = _get_base_font_or_default(); + + rids.clear(); + if (fallbacks.is_empty() && f.is_valid()) { + RID rid = _get_rid(); + if (rid.is_valid()) { + rids.push_back(rid); + } + + const TypedArray &base_fallbacks = f->get_fallbacks(); + for (int i = 0; i < base_fallbacks.size(); i++) { + _update_rids_fb(base_fallbacks[i], 0); + } + } else { + _update_rids_fb(const_cast(this), 0); + } + dirty_rids = false; +} + +void SystemFont::_update_base_font() { + if (base_font.is_valid()) { + base_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast(this), &Font::_invalidate_rids)); + base_font.unref(); + } + + face_indeces.clear(); + ftr_weight = 0; + ftr_italic = 0; + for (const String &E : names) { + if (E.is_empty()) { + continue; + } + + String path = OS::get_singleton()->get_system_font_path(E, style & TextServer::FONT_BOLD, style & TextServer::FONT_ITALIC); + if (path.is_empty()) { + continue; + } + Ref file; + file.instantiate(); + Error err = file->load_dynamic_font(path); + if (err != OK) { + continue; + } + + // If it's a font collection check all faces to match requested style. + for (int i = 0; i < file->get_face_count(); i++) { + file->set_face_index(0, i); + if (((file->get_font_style() & TextServer::FONT_BOLD) == (style & TextServer::FONT_BOLD)) && ((file->get_font_style() & TextServer::FONT_ITALIC) == (style & TextServer::FONT_ITALIC))) { + face_indeces.push_back(i); + } + } + if (face_indeces.is_empty()) { + face_indeces.push_back(0); + } + file->set_face_index(0, face_indeces[0]); + + // If it's a variable font, apply weight and italic coordinates to match requested style. + Dictionary ftr = file->get_supported_variation_list(); + if ((style & TextServer::FONT_BOLD) && ftr.has(TS->name_to_tag("weight"))) { + ftr_weight = 700; + } + if ((style & TextServer::FONT_ITALIC) && ftr.has(TS->name_to_tag("italic"))) { + ftr_italic = 1; + } + + // Apply font rendering settings. + file->set_antialiased(antialiased); + file->set_generate_mipmaps(mipmaps); + file->set_force_autohinter(force_autohinter); + file->set_hinting(hinting); + file->set_subpixel_positioning(subpixel_positioning); + file->set_oversampling(oversampling); + + base_font = file; + } + + if (base_font.is_valid()) { + base_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast(this), &Font::_invalidate_rids), varray(), CONNECT_REFERENCE_COUNTED); + } + + _invalidate_rids(); + notify_property_list_changed(); +} + +void SystemFont::reset_state() { + if (base_font.is_valid()) { + base_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast(this), &Font::_invalidate_rids)); + base_font.unref(); + } + + if (theme_font.is_valid()) { + theme_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast(this), &Font::_invalidate_rids)); + theme_font.unref(); + } + + names.clear(); + face_indeces.clear(); + ftr_weight = 0; + ftr_italic = 0; + style = 0; + antialiased = true; + mipmaps = false; + force_autohinter = false; + hinting = TextServer::HINTING_LIGHT; + subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_DISABLED; + oversampling = 0.f; + + Font::reset_state(); +} + +Ref SystemFont::_get_base_font_or_default() const { + if (theme_font.is_valid()) { + theme_font->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast(const_cast(this)), &Font::_invalidate_rids)); + theme_font.unref(); + } + + if (base_font.is_valid()) { + return base_font; + } + + // Check the project-defined Theme resource. + if (Theme::get_project_default().is_valid()) { + List theme_types; + Theme::get_project_default()->get_type_dependencies(get_class_name(), StringName(), &theme_types); + + for (const StringName &E : theme_types) { + if (Theme::get_project_default()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) { + Ref f = Theme::get_project_default()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); + if (f.is_valid()) { + theme_font = f; + theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast(const_cast(this)), &Font::_invalidate_rids), varray(), CONNECT_REFERENCE_COUNTED); + } + return f; + } + } + } + + // Lastly, fall back on the items defined in the default Theme, if they exist. + if (Theme::get_default().is_valid()) { + List theme_types; + Theme::get_default()->get_type_dependencies(get_class_name(), StringName(), &theme_types); + + for (const StringName &E : theme_types) { + if (Theme::get_default()->has_theme_item(Theme::DATA_TYPE_FONT, "font", E)) { + Ref f = Theme::get_default()->get_theme_item(Theme::DATA_TYPE_FONT, "font", E); + if (f.is_valid()) { + theme_font = f; + theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast(const_cast(this)), &Font::_invalidate_rids), varray(), CONNECT_REFERENCE_COUNTED); + } + return f; + } + } + + // If they don't exist, use any type to return the default/empty value. + Ref f = Theme::get_default()->get_theme_item(Theme::DATA_TYPE_FONT, "font", StringName()); + if (f.is_valid()) { + theme_font = f; + theme_font->connect(CoreStringNames::get_singleton()->changed, callable_mp(reinterpret_cast(const_cast(this)), &Font::_invalidate_rids), varray(), CONNECT_REFERENCE_COUNTED); + } + return f; + } + + return Ref(); +} + +void SystemFont::set_antialiased(bool p_antialiased) { + if (antialiased != p_antialiased) { + antialiased = p_antialiased; + if (base_font.is_valid()) { + base_font->set_antialiased(antialiased); + } + emit_changed(); + } +} + +bool SystemFont::is_antialiased() const { + return antialiased; +} + +void SystemFont::set_generate_mipmaps(bool p_generate_mipmaps) { + if (mipmaps != p_generate_mipmaps) { + mipmaps = p_generate_mipmaps; + if (base_font.is_valid()) { + base_font->set_generate_mipmaps(mipmaps); + } + emit_changed(); + } +} + +bool SystemFont::get_generate_mipmaps() const { + return mipmaps; +} + +void SystemFont::set_force_autohinter(bool p_force_autohinter) { + if (force_autohinter != p_force_autohinter) { + force_autohinter = p_force_autohinter; + if (base_font.is_valid()) { + base_font->set_force_autohinter(force_autohinter); + } + emit_changed(); + } +} + +bool SystemFont::is_force_autohinter() const { + return force_autohinter; +} + +void SystemFont::set_hinting(TextServer::Hinting p_hinting) { + if (hinting != p_hinting) { + hinting = p_hinting; + if (base_font.is_valid()) { + base_font->set_hinting(hinting); + } + emit_changed(); + } +} + +TextServer::Hinting SystemFont::get_hinting() const { + return hinting; +} + +void SystemFont::set_subpixel_positioning(TextServer::SubpixelPositioning p_subpixel) { + if (subpixel_positioning != p_subpixel) { + subpixel_positioning = p_subpixel; + if (base_font.is_valid()) { + base_font->set_subpixel_positioning(subpixel_positioning); + } + emit_changed(); + } +} + +TextServer::SubpixelPositioning SystemFont::get_subpixel_positioning() const { + return subpixel_positioning; +} + +void SystemFont::set_oversampling(real_t p_oversampling) { + if (oversampling != p_oversampling) { + oversampling = p_oversampling; + if (base_font.is_valid()) { + base_font->set_oversampling(oversampling); + } + emit_changed(); + } +} + +real_t SystemFont::get_oversampling() const { + return oversampling; +} + +void SystemFont::set_font_names(const PackedStringArray &p_names) { + if (names != p_names) { + names = p_names; + _update_base_font(); + } +} + +PackedStringArray SystemFont::get_font_names() const { + return names; +} + +void SystemFont::set_font_style(BitField p_style) { + if (style != p_style) { + style = p_style; + _update_base_font(); + } +} + +BitField SystemFont::get_font_style() const { + return style; +} + +int SystemFont::get_spacing(TextServer::SpacingType p_spacing) const { + if (base_font.is_valid()) { + return base_font->get_spacing(p_spacing); + } else { + return 0; + } +} + +RID SystemFont::find_variation(const Dictionary &p_variation_coordinates, int p_face_index, float p_strength, Transform2D p_transform) const { + Ref f = _get_base_font_or_default(); + if (f.is_valid()) { + Dictionary var = p_variation_coordinates; + if (ftr_weight > 0 && !var.has(TS->name_to_tag("weight"))) { + var[TS->name_to_tag("weight")] = ftr_weight; + } + if (ftr_italic > 0 && !var.has(TS->name_to_tag("italic"))) { + var[TS->name_to_tag("italic")] = ftr_italic; + } + + if (!face_indeces.is_empty()) { + int face_index = CLAMP(p_face_index, 0, face_indeces.size() - 1); + return f->find_variation(var, face_indeces[face_index], p_strength, p_transform); + } else { + return f->find_variation(var, 0, p_strength, p_transform); + } + } + return RID(); +} + +RID SystemFont::_get_rid() const { + Ref f = _get_base_font_or_default(); + if (f.is_valid()) { + if (!face_indeces.is_empty()) { + Dictionary var; + if (ftr_weight > 0) { + var[TS->name_to_tag("weight")] = ftr_weight; + } + if (ftr_italic > 0) { + var[TS->name_to_tag("italic")] = ftr_italic; + } + return f->find_variation(var, face_indeces[0]); + } else { + return f->_get_rid(); + } + } + return RID(); +} + +int64_t SystemFont::get_face_count() const { + return face_indeces.size(); +} + +SystemFont::SystemFont() { + /* NOP */ +} + +SystemFont::~SystemFont() { + reset_state(); +} diff --git a/scene/resources/font.h b/scene/resources/font.h index 8a2f83c4145..260b4e521fe 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -41,7 +41,7 @@ class TextLine; class TextParagraph; /*************************************************************************/ -/* Font */ +/* Font */ /*************************************************************************/ class Font : public Resource { @@ -381,4 +381,74 @@ public: ~FontVariation(); }; +/*************************************************************************/ +/* SystemFont */ +/*************************************************************************/ + +class SystemFont : public Font { + GDCLASS(SystemFont, Font); + + PackedStringArray names; + BitField style = 0; + + mutable Ref theme_font; + + Ref base_font; + Vector face_indeces; + int ftr_weight = 0; + int ftr_italic = 0; + + bool antialiased = true; + bool mipmaps = false; + bool force_autohinter = false; + TextServer::Hinting hinting = TextServer::HINTING_LIGHT; + TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; + real_t oversampling = 0.f; + +protected: + static void _bind_methods(); + + virtual void _update_base_font(); + virtual void _update_rids() const override; + + virtual void reset_state() override; + +public: + virtual Ref _get_base_font_or_default() const; + + virtual void set_antialiased(bool p_antialiased); + virtual bool is_antialiased() const; + + virtual void set_generate_mipmaps(bool p_generate_mipmaps); + virtual bool get_generate_mipmaps() const; + + virtual void set_force_autohinter(bool p_force_autohinter); + virtual bool is_force_autohinter() const; + + 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; + + virtual void set_font_names(const PackedStringArray &p_names); + virtual PackedStringArray get_font_names() const; + + virtual void set_font_style(BitField p_style); + virtual BitField get_font_style() const override; + + virtual int get_spacing(TextServer::SpacingType p_spacing) const override; + + virtual RID find_variation(const Dictionary &p_variation_coordinates, int p_face_index = 0, float p_strength = 0.0, Transform2D p_transform = Transform2D()) const override; + virtual RID _get_rid() const override; + + int64_t get_face_count() const override; + + SystemFont(); + ~SystemFont(); +}; + #endif // FONT_H