diff options
author | bruvzg <7645683+bruvzg@users.noreply.github.com> | 2022-11-21 15:04:01 +0200 |
---|---|---|
committer | bruvzg <7645683+bruvzg@users.noreply.github.com> | 2022-12-04 18:44:20 +0200 |
commit | ecec415988de5b016c70512bbb6a7cfc04ccd0a2 (patch) | |
tree | f7c4b665d8b956d4210d48131c85f4c6bb0d136e /platform/android | |
parent | 015dc492de33a41eaeb14c0503a6be10466fe457 (diff) |
Use system fonts as fallback and improve system font handling.
Add support for font weight and stretch selection when using system fonts.
Add function to get system fallback font from a font name, style, text, and language code.
Implement system font support for Android.
Use system fonts as a last resort fallback.
Diffstat (limited to 'platform/android')
-rw-r--r-- | platform/android/export/export_plugin.cpp | 4 | ||||
-rw-r--r-- | platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt | 5 | ||||
-rw-r--r-- | platform/android/os_android.cpp | 226 | ||||
-rw-r--r-- | platform/android/os_android.h | 21 |
4 files changed, 254 insertions, 2 deletions
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 737e25b270..795a542ed5 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -703,7 +703,7 @@ Error EditorExportPlatformAndroid::save_apk_so(void *p_userdata, const SharedObj exported = true; String abi = abis[abi_index].abi; String dst_path = String("lib").path_join(abi).path_join(p_so.path.get_file()); - Vector<uint8_t> array = FileAccess::get_file_as_array(p_so.path); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(p_so.path); Error store_err = store_in_apk(ed, dst_path, array); ERR_FAIL_COND_V_MSG(store_err, store_err, "Cannot store in apk file '" + dst_path + "'."); } @@ -748,7 +748,7 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared String abi = abis[abi_index].abi; String filename = p_so.path.get_file(); String dst_path = base.path_join(type).path_join(abi).path_join(filename); - Vector<uint8_t> data = FileAccess::get_file_as_array(p_so.path); + Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_so.path); print_verbose("Copying .so file from " + p_so.path + " to " + dst_path); Error err = store_file_at_path(dst_path, data); ERR_FAIL_COND_V_MSG(err, err, "Failed to copy .so file from " + p_so.path + " to " + dst_path); diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt index c9282dd247..1a3576a6a9 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt @@ -90,6 +90,11 @@ internal enum class StorageScope { return APP } + var rootDir: String? = System.getenv("ANDROID_ROOT") + if (rootDir != null && canonicalPathFile.startsWith(rootDir)) { + return APP + } + if (sharedDir != null && canonicalPathFile.startsWith(sharedDir)) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { // Before R, apps had access to shared storage so long as they have the right diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 4f81e4bccd..317a63f21f 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -337,6 +337,229 @@ String OS_Android::get_data_path() const { return get_user_data_dir(); } +void OS_Android::_load_system_font_config() { + font_aliases.clear(); + fonts.clear(); + font_names.clear(); + + Ref<XMLParser> parser; + parser.instantiate(); + + Error err = parser->open(String(getenv("ANDROID_ROOT")).path_join("/etc/fonts.xml")); + if (err == OK) { + bool in_font_node = false; + String fb, fn; + FontInfo fi; + + while (parser->read() == OK) { + if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { + in_font_node = false; + if (parser->get_node_name() == "familyset") { + int ver = parser->has_attribute("version") ? parser->get_attribute_value("version").to_int() : 0; + if (ver < 21) { + ERR_PRINT(vformat("Unsupported font config version %s", ver)); + break; + } + } else if (parser->get_node_name() == "alias") { + String name = parser->has_attribute("name") ? parser->get_attribute_value("name").strip_edges() : String(); + String to = parser->has_attribute("to") ? parser->get_attribute_value("to").strip_edges() : String(); + if (!name.is_empty() && !to.is_empty()) { + font_aliases[name] = to; + } + } else if (parser->get_node_name() == "family") { + fn = parser->has_attribute("name") ? parser->get_attribute_value("name").strip_edges() : String(); + String lang_code = parser->has_attribute("lang") ? parser->get_attribute_value("lang").strip_edges() : String(); + Vector<String> lang_codes = lang_code.split(","); + for (int i = 0; i < lang_codes.size(); i++) { + Vector<String> lang_code_elements = lang_codes[i].split("-"); + if (lang_code_elements.size() >= 1 && lang_code_elements[0] != "und") { + // Add missing script codes. + if (lang_code_elements[0] == "ko") { + fi.script.insert("Hani"); + fi.script.insert("Hang"); + } + if (lang_code_elements[0] == "ja") { + fi.script.insert("Hani"); + fi.script.insert("Kana"); + fi.script.insert("Hira"); + } + if (!lang_code_elements[0].is_empty()) { + fi.lang.insert(lang_code_elements[0]); + } + } + if (lang_code_elements.size() >= 2) { + // Add common codes for variants and remove variants not supported by HarfBuzz/ICU. + if (lang_code_elements[1] == "Aran") { + fi.script.insert("Arab"); + } + if (lang_code_elements[1] == "Cyrs") { + fi.script.insert("Cyrl"); + } + if (lang_code_elements[1] == "Hanb") { + fi.script.insert("Hani"); + fi.script.insert("Bopo"); + } + if (lang_code_elements[1] == "Hans" || lang_code_elements[1] == "Hant") { + fi.script.insert("Hani"); + } + if (lang_code_elements[1] == "Syrj" || lang_code_elements[1] == "Syre" || lang_code_elements[1] == "Syrn") { + fi.script.insert("Syrc"); + } + if (!lang_code_elements[1].is_empty() && lang_code_elements[1] != "Zsym" && lang_code_elements[1] != "Zsye" && lang_code_elements[1] != "Zmth") { + fi.script.insert(lang_code_elements[1]); + } + } + } + } else if (parser->get_node_name() == "font") { + in_font_node = true; + fb = parser->has_attribute("fallbackFor") ? parser->get_attribute_value("fallbackFor").strip_edges() : String(); + fi.weight = parser->has_attribute("weight") ? parser->get_attribute_value("weight").to_int() : 400; + fi.italic = parser->has_attribute("style") && parser->get_attribute_value("style").strip_edges() == "italic"; + } + } + if (parser->get_node_type() == XMLParser::NODE_TEXT) { + if (in_font_node) { + fi.filename = parser->get_node_data().strip_edges(); + fi.font_name = fn; + if (!fb.is_empty() && fn.is_empty()) { + fi.font_name = fb; + fi.priority = 2; + } + if (fi.font_name.is_empty()) { + fi.font_name = "sans-serif"; + fi.priority = 5; + } + if (fi.font_name.ends_with("-condensed")) { + fi.stretch = 75; + fi.font_name = fi.font_name.trim_suffix("-condensed"); + } + fonts.push_back(fi); + font_names.insert(fi.font_name); + } + } + if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { + in_font_node = false; + if (parser->get_node_name() == "font") { + fb = String(); + fi.font_name = String(); + fi.priority = 0; + fi.weight = 400; + fi.stretch = 100; + fi.italic = false; + } else if (parser->get_node_name() == "family") { + fi = FontInfo(); + fn = String(); + } + } + } + parser->close(); + } else { + ERR_PRINT("Unable to load font config"); + } + + font_config_loaded = true; +} + +Vector<String> OS_Android::get_system_fonts() const { + if (!font_config_loaded) { + const_cast<OS_Android *>(this)->_load_system_font_config(); + } + Vector<String> ret; + for (const String &E : font_names) { + ret.push_back(E); + } + return ret; +} + +Vector<String> OS_Android::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const { + if (!font_config_loaded) { + const_cast<OS_Android *>(this)->_load_system_font_config(); + } + String font_name = p_font_name.to_lower(); + if (font_aliases.has(font_name)) { + font_name = font_aliases[font_name]; + } + String root = String(getenv("ANDROID_ROOT")).path_join("fonts"); + String lang_prefix = p_locale.split("_")[0]; + Vector<String> ret; + int best_score = 0; + for (const List<FontInfo>::Element *E = fonts.front(); E; E = E->next()) { + int score = 0; + if (!E->get().script.is_empty() && !p_script.is_empty() && !E->get().script.has(p_script)) { + continue; + } + float sim = E->get().font_name.similarity(font_name); + if (sim > 0.0) { + score += (60 * sim + 5 - E->get().priority); + } + if (E->get().lang.has(p_locale)) { + score += 120; + } else if (E->get().lang.has(lang_prefix)) { + score += 115; + } + if (E->get().script.has(p_script)) { + score += 240; + } + score += (20 - Math::abs(E->get().weight - p_weight) / 50); + score += (20 - Math::abs(E->get().stretch - p_stretch) / 10); + if (E->get().italic == p_italic) { + score += 30; + } + if (score > best_score) { + best_score = score; + if (ret.find(root.path_join(E->get().filename)) < 0) { + ret.insert(0, root.path_join(E->get().filename)); + } + } else if (score == best_score || E->get().script.is_empty()) { + if (ret.find(root.path_join(E->get().filename)) < 0) { + ret.push_back(root.path_join(E->get().filename)); + } + } + if (score >= 490) { + break; // Perfect match. + } + } + + return ret; +} + +String OS_Android::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const { + if (!font_config_loaded) { + const_cast<OS_Android *>(this)->_load_system_font_config(); + } + String font_name = p_font_name.to_lower(); + if (font_aliases.has(font_name)) { + font_name = font_aliases[font_name]; + } + String root = String(getenv("ANDROID_ROOT")).path_join("fonts"); + + int best_score = 0; + const List<FontInfo>::Element *best_match = nullptr; + + for (const List<FontInfo>::Element *E = fonts.front(); E; E = E->next()) { + int score = 0; + if (E->get().font_name == font_name) { + score += (65 - E->get().priority); + } + score += (20 - Math::abs(E->get().weight - p_weight) / 50); + score += (20 - Math::abs(E->get().stretch - p_stretch) / 10); + if (E->get().italic == p_italic) { + score += 30; + } + if (score >= 60 && score > best_score) { + best_score = score; + best_match = E; + } + if (score >= 140) { + break; // Perfect match. + } + } + if (best_match) { + return root.path_join(best_match->get().filename); + } + return String(); +} + String OS_Android::get_executable_path() const { // Since unix process creation is restricted on Android, we bypass // OS_Unix::get_executable_path() so we can return ANDROID_EXEC_PATH. @@ -449,6 +672,9 @@ String OS_Android::get_config_path() const { } bool OS_Android::_check_internal_feature_support(const String &p_feature) { + if (p_feature == "system_fonts") { + return true; + } if (p_feature == "mobile") { return true; } diff --git a/platform/android/os_android.h b/platform/android/os_android.h index d6546a3507..9034615fc4 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -62,9 +62,26 @@ private: MainLoop *main_loop = nullptr; + struct FontInfo { + String font_name; + HashSet<String> lang; + HashSet<String> script; + int weight = 400; + int stretch = 100; + bool italic = false; + int priority = 0; + String filename; + }; + + HashMap<String, String> font_aliases; + List<FontInfo> fonts; + HashSet<String> font_names; + bool font_config_loaded = false; + GodotJavaWrapper *godot_java = nullptr; GodotIOJavaWrapper *godot_io_java = nullptr; + void _load_system_font_config(); String get_system_property(const char *key) const; public: @@ -114,6 +131,10 @@ public: ANativeWindow *get_native_window() const; virtual Error shell_open(String p_uri) override; + + virtual Vector<String> get_system_fonts() const override; + virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; + virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; virtual String get_executable_path() const override; virtual String get_user_data_dir() const override; virtual String get_data_path() const override; |