summaryrefslogtreecommitdiff
path: root/core/string
diff options
context:
space:
mode:
Diffstat (limited to 'core/string')
-rw-r--r--core/string/SCsub7
-rw-r--r--core/string/node_path.cpp436
-rw-r--r--core/string/node_path.h97
-rw-r--r--core/string/optimized_translation.cpp289
-rw-r--r--core/string/optimized_translation.h89
-rw-r--r--core/string/print_string.cpp110
-rw-r--r--core/string/print_string.h58
-rw-r--r--core/string/string_buffer.h162
-rw-r--r--core/string/string_builder.cpp99
-rw-r--r--core/string/string_builder.h84
-rw-r--r--core/string/string_name.cpp479
-rw-r--r--core/string/string_name.h186
-rw-r--r--core/string/translation.cpp1595
-rw-r--r--core/string/translation.h158
-rw-r--r--core/string/translation_po.cpp309
-rw-r--r--core/string/translation_po.h92
-rw-r--r--core/string/ucaps.h1415
-rw-r--r--core/string/ustring.cpp4922
-rw-r--r--core/string/ustring.h561
19 files changed, 11148 insertions, 0 deletions
diff --git a/core/string/SCsub b/core/string/SCsub
new file mode 100644
index 0000000000..3217166f18
--- /dev/null
+++ b/core/string/SCsub
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+
+Import("env")
+
+env_string = env.Clone()
+
+env_string.add_source_files(env.core_sources, "*.cpp")
diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp
new file mode 100644
index 0000000000..5fae13779e
--- /dev/null
+++ b/core/string/node_path.cpp
@@ -0,0 +1,436 @@
+/*************************************************************************/
+/* node_path.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "node_path.h"
+
+#include "core/string/print_string.h"
+
+void NodePath::_update_hash_cache() const {
+ uint32_t h = data->absolute ? 1 : 0;
+ int pc = data->path.size();
+ const StringName *sn = data->path.ptr();
+ for (int i = 0; i < pc; i++) {
+ h = h ^ sn[i].hash();
+ }
+ int spc = data->subpath.size();
+ const StringName *ssn = data->subpath.ptr();
+ for (int i = 0; i < spc; i++) {
+ h = h ^ ssn[i].hash();
+ }
+
+ data->hash_cache_valid = true;
+ data->hash_cache = h;
+}
+
+void NodePath::prepend_period() {
+ if (data->path.size() && data->path[0].operator String() != ".") {
+ data->path.insert(0, ".");
+ data->hash_cache_valid = false;
+ }
+}
+
+bool NodePath::is_absolute() const {
+ if (!data) {
+ return false;
+ }
+
+ return data->absolute;
+}
+
+int NodePath::get_name_count() const {
+ if (!data) {
+ return 0;
+ }
+
+ return data->path.size();
+}
+
+StringName NodePath::get_name(int p_idx) const {
+ ERR_FAIL_COND_V(!data, StringName());
+ ERR_FAIL_INDEX_V(p_idx, data->path.size(), StringName());
+ return data->path[p_idx];
+}
+
+int NodePath::get_subname_count() const {
+ if (!data) {
+ return 0;
+ }
+
+ return data->subpath.size();
+}
+
+StringName NodePath::get_subname(int p_idx) const {
+ ERR_FAIL_COND_V(!data, StringName());
+ ERR_FAIL_INDEX_V(p_idx, data->subpath.size(), StringName());
+ return data->subpath[p_idx];
+}
+
+void NodePath::unref() {
+ if (data && data->refcount.unref()) {
+ memdelete(data);
+ }
+ data = nullptr;
+}
+
+bool NodePath::operator==(const NodePath &p_path) const {
+ if (data == p_path.data) {
+ return true;
+ }
+
+ if (!data || !p_path.data) {
+ return false;
+ }
+
+ if (data->absolute != p_path.data->absolute) {
+ return false;
+ }
+
+ int path_size = data->path.size();
+
+ if (path_size != p_path.data->path.size()) {
+ return false;
+ }
+
+ int subpath_size = data->subpath.size();
+
+ if (subpath_size != p_path.data->subpath.size()) {
+ return false;
+ }
+
+ const StringName *l_path_ptr = data->path.ptr();
+ const StringName *r_path_ptr = p_path.data->path.ptr();
+
+ for (int i = 0; i < path_size; i++) {
+ if (l_path_ptr[i] != r_path_ptr[i]) {
+ return false;
+ }
+ }
+
+ const StringName *l_subpath_ptr = data->subpath.ptr();
+ const StringName *r_subpath_ptr = p_path.data->subpath.ptr();
+
+ for (int i = 0; i < subpath_size; i++) {
+ if (l_subpath_ptr[i] != r_subpath_ptr[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool NodePath::operator!=(const NodePath &p_path) const {
+ return (!(*this == p_path));
+}
+
+void NodePath::operator=(const NodePath &p_path) {
+ if (this == &p_path) {
+ return;
+ }
+
+ unref();
+
+ if (p_path.data && p_path.data->refcount.ref()) {
+ data = p_path.data;
+ }
+}
+
+NodePath::operator String() const {
+ if (!data) {
+ return String();
+ }
+
+ String ret;
+ if (data->absolute) {
+ ret = "/";
+ }
+
+ for (int i = 0; i < data->path.size(); i++) {
+ if (i > 0) {
+ ret += "/";
+ }
+ ret += data->path[i].operator String();
+ }
+
+ for (int i = 0; i < data->subpath.size(); i++) {
+ ret += ":" + data->subpath[i].operator String();
+ }
+
+ return ret;
+}
+
+Vector<StringName> NodePath::get_names() const {
+ if (data) {
+ return data->path;
+ }
+ return Vector<StringName>();
+}
+
+Vector<StringName> NodePath::get_subnames() const {
+ if (data) {
+ return data->subpath;
+ }
+ return Vector<StringName>();
+}
+
+StringName NodePath::get_concatenated_subnames() const {
+ ERR_FAIL_COND_V(!data, StringName());
+
+ if (!data->concatenated_subpath) {
+ int spc = data->subpath.size();
+ String concatenated;
+ const StringName *ssn = data->subpath.ptr();
+ for (int i = 0; i < spc; i++) {
+ concatenated += i == 0 ? ssn[i].operator String() : ":" + ssn[i];
+ }
+ data->concatenated_subpath = concatenated;
+ }
+ return data->concatenated_subpath;
+}
+
+NodePath NodePath::rel_path_to(const NodePath &p_np) const {
+ ERR_FAIL_COND_V(!is_absolute(), NodePath());
+ ERR_FAIL_COND_V(!p_np.is_absolute(), NodePath());
+
+ Vector<StringName> src_dirs = get_names();
+ Vector<StringName> dst_dirs = p_np.get_names();
+
+ //find common parent
+ int common_parent = 0;
+
+ while (true) {
+ if (src_dirs.size() == common_parent) {
+ break;
+ }
+ if (dst_dirs.size() == common_parent) {
+ break;
+ }
+ if (src_dirs[common_parent] != dst_dirs[common_parent]) {
+ break;
+ }
+ common_parent++;
+ }
+
+ common_parent--;
+
+ Vector<StringName> relpath;
+ relpath.resize(src_dirs.size() + dst_dirs.size() + 1);
+
+ StringName *relpath_ptr = relpath.ptrw();
+
+ int path_size = 0;
+ StringName back_str("..");
+ for (int i = common_parent + 1; i < src_dirs.size(); i++) {
+ relpath_ptr[path_size++] = back_str;
+ }
+
+ for (int i = common_parent + 1; i < dst_dirs.size(); i++) {
+ relpath_ptr[path_size++] = dst_dirs[i];
+ }
+
+ if (path_size == 0) {
+ relpath_ptr[path_size++] = ".";
+ }
+
+ relpath.resize(path_size);
+
+ return NodePath(relpath, p_np.get_subnames(), false);
+}
+
+NodePath NodePath::get_as_property_path() const {
+ if (!data || !data->path.size()) {
+ return *this;
+ } else {
+ Vector<StringName> new_path = data->subpath;
+
+ String initial_subname = data->path[0];
+
+ for (int i = 1; i < data->path.size(); i++) {
+ initial_subname += "/" + data->path[i];
+ }
+ new_path.insert(0, initial_subname);
+
+ return NodePath(Vector<StringName>(), new_path, false);
+ }
+}
+
+bool NodePath::is_empty() const {
+ return !data;
+}
+
+void NodePath::simplify() {
+ if (!data) {
+ return;
+ }
+ for (int i = 0; i < data->path.size(); i++) {
+ if (data->path.size() == 1) {
+ break;
+ }
+ if (data->path[i].operator String() == ".") {
+ data->path.remove(i);
+ i--;
+ } else if (i > 0 && data->path[i].operator String() == ".." && data->path[i - 1].operator String() != "." && data->path[i - 1].operator String() != "..") {
+ //remove both
+ data->path.remove(i - 1);
+ data->path.remove(i - 1);
+ i -= 2;
+ if (data->path.size() == 0) {
+ data->path.push_back(".");
+ break;
+ }
+ }
+ }
+ data->hash_cache_valid = false;
+}
+
+NodePath NodePath::simplified() const {
+ NodePath np = *this;
+ np.simplify();
+ return np;
+}
+
+NodePath::NodePath(const Vector<StringName> &p_path, bool p_absolute) {
+ if (p_path.size() == 0) {
+ return;
+ }
+
+ data = memnew(Data);
+ data->refcount.init();
+ data->absolute = p_absolute;
+ data->path = p_path;
+ data->has_slashes = true;
+ data->hash_cache_valid = false;
+}
+
+NodePath::NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute) {
+ if (p_path.size() == 0 && p_subpath.size() == 0) {
+ return;
+ }
+
+ data = memnew(Data);
+ data->refcount.init();
+ data->absolute = p_absolute;
+ data->path = p_path;
+ data->subpath = p_subpath;
+ data->has_slashes = true;
+ data->hash_cache_valid = false;
+}
+
+NodePath::NodePath(const NodePath &p_path) {
+ if (p_path.data && p_path.data->refcount.ref()) {
+ data = p_path.data;
+ }
+}
+
+NodePath::NodePath(const String &p_path) {
+ if (p_path.length() == 0) {
+ return;
+ }
+
+ String path = p_path;
+ Vector<StringName> subpath;
+
+ bool absolute = (path[0] == '/');
+ bool last_is_slash = true;
+ bool has_slashes = false;
+ int slices = 0;
+ int subpath_pos = path.find(":");
+
+ if (subpath_pos != -1) {
+ int from = subpath_pos + 1;
+
+ for (int i = from; i <= path.length(); i++) {
+ if (path[i] == ':' || path[i] == 0) {
+ String str = path.substr(from, i - from);
+ if (str == "") {
+ if (path[i] == 0) {
+ continue; // Allow end-of-path :
+ }
+
+ ERR_FAIL_MSG("Invalid NodePath '" + p_path + "'.");
+ }
+ subpath.push_back(str);
+
+ from = i + 1;
+ }
+ }
+
+ path = path.substr(0, subpath_pos);
+ }
+
+ for (int i = (int)absolute; i < path.length(); i++) {
+ if (path[i] == '/') {
+ last_is_slash = true;
+ has_slashes = true;
+ } else {
+ if (last_is_slash) {
+ slices++;
+ }
+
+ last_is_slash = false;
+ }
+ }
+
+ if (slices == 0 && !absolute && !subpath.size()) {
+ return;
+ }
+
+ data = memnew(Data);
+ data->refcount.init();
+ data->absolute = absolute;
+ data->has_slashes = has_slashes;
+ data->subpath = subpath;
+ data->hash_cache_valid = false;
+
+ if (slices == 0) {
+ return;
+ }
+ data->path.resize(slices);
+ last_is_slash = true;
+ int from = (int)absolute;
+ int slice = 0;
+
+ for (int i = (int)absolute; i < path.length() + 1; i++) {
+ if (path[i] == '/' || path[i] == 0) {
+ if (!last_is_slash) {
+ String name = path.substr(from, i - from);
+ ERR_FAIL_INDEX(slice, data->path.size());
+ data->path.write[slice++] = name;
+ }
+ from = i + 1;
+ last_is_slash = true;
+ } else {
+ last_is_slash = false;
+ }
+ }
+}
+
+NodePath::~NodePath() {
+ unref();
+}
diff --git a/core/string/node_path.h b/core/string/node_path.h
new file mode 100644
index 0000000000..a277ab26fa
--- /dev/null
+++ b/core/string/node_path.h
@@ -0,0 +1,97 @@
+/*************************************************************************/
+/* node_path.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef NODE_PATH_H
+#define NODE_PATH_H
+
+#include "core/string/string_name.h"
+#include "core/string/ustring.h"
+
+class NodePath {
+ struct Data {
+ SafeRefCount refcount;
+ Vector<StringName> path;
+ Vector<StringName> subpath;
+ StringName concatenated_subpath;
+ bool absolute;
+ bool has_slashes;
+ mutable bool hash_cache_valid;
+ mutable uint32_t hash_cache;
+ };
+
+ mutable Data *data = nullptr;
+ void unref();
+
+ void _update_hash_cache() const;
+
+public:
+ bool is_absolute() const;
+ int get_name_count() const;
+ StringName get_name(int p_idx) const;
+ int get_subname_count() const;
+ StringName get_subname(int p_idx) const;
+ Vector<StringName> get_names() const;
+ Vector<StringName> get_subnames() const;
+ StringName get_concatenated_subnames() const;
+
+ NodePath rel_path_to(const NodePath &p_np) const;
+ NodePath get_as_property_path() const;
+
+ void prepend_period();
+
+ _FORCE_INLINE_ uint32_t hash() const {
+ if (!data) {
+ return 0;
+ }
+ if (!data->hash_cache_valid) {
+ _update_hash_cache();
+ }
+ return data->hash_cache;
+ }
+
+ operator String() const;
+ bool is_empty() const;
+
+ bool operator==(const NodePath &p_path) const;
+ bool operator!=(const NodePath &p_path) const;
+ void operator=(const NodePath &p_path);
+
+ void simplify();
+ NodePath simplified() const;
+
+ NodePath(const Vector<StringName> &p_path, bool p_absolute);
+ NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute);
+ NodePath(const NodePath &p_path);
+ NodePath(const String &p_path);
+ NodePath() {}
+ ~NodePath();
+};
+
+#endif // NODE_PATH_H
diff --git a/core/string/optimized_translation.cpp b/core/string/optimized_translation.cpp
new file mode 100644
index 0000000000..5863bd1c46
--- /dev/null
+++ b/core/string/optimized_translation.cpp
@@ -0,0 +1,289 @@
+/*************************************************************************/
+/* optimized_translation.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "optimized_translation.h"
+
+#include "core/templates/pair.h"
+
+extern "C" {
+#include "thirdparty/misc/smaz.h"
+}
+
+struct CompressedString {
+ int orig_len;
+ CharString compressed;
+ int offset;
+};
+
+void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
+ // This method compresses a Translation instance.
+ // Right now, it doesn't handle context or plurals, so Translation subclasses using plurals or context (i.e TranslationPO) shouldn't be compressed.
+#ifdef TOOLS_ENABLED
+ ERR_FAIL_COND(p_from.is_null());
+ List<StringName> keys;
+ p_from->get_message_list(&keys);
+
+ int size = Math::larger_prime(keys.size());
+
+ Vector<Vector<Pair<int, CharString>>> buckets;
+ Vector<Map<uint32_t, int>> table;
+ Vector<uint32_t> hfunc_table;
+ Vector<CompressedString> compressed;
+
+ table.resize(size);
+ hfunc_table.resize(size);
+ buckets.resize(size);
+ compressed.resize(keys.size());
+
+ int idx = 0;
+ int total_compression_size = 0;
+ int total_string_size = 0;
+
+ for (const StringName &E : keys) {
+ //hash string
+ CharString cs = E.operator String().utf8();
+ uint32_t h = hash(0, cs.get_data());
+ Pair<int, CharString> p;
+ p.first = idx;
+ p.second = cs;
+ buckets.write[h % size].push_back(p);
+
+ //compress string
+ CharString src_s = p_from->get_message(E).operator String().utf8();
+ CompressedString ps;
+ ps.orig_len = src_s.size();
+ ps.offset = total_compression_size;
+
+ if (ps.orig_len != 0) {
+ CharString dst_s;
+ dst_s.resize(src_s.size());
+ int ret = smaz_compress(src_s.get_data(), src_s.size(), dst_s.ptrw(), src_s.size());
+ if (ret >= src_s.size()) {
+ //if compressed is larger than original, just use original
+ ps.orig_len = src_s.size();
+ ps.compressed = src_s;
+ } else {
+ dst_s.resize(ret);
+ //ps.orig_len=;
+ ps.compressed = dst_s;
+ }
+ } else {
+ ps.orig_len = 1;
+ ps.compressed.resize(1);
+ ps.compressed[0] = 0;
+ }
+
+ compressed.write[idx] = ps;
+ total_compression_size += ps.compressed.size();
+ total_string_size += src_s.size();
+ idx++;
+ }
+
+ int bucket_table_size = 0;
+
+ for (int i = 0; i < size; i++) {
+ const Vector<Pair<int, CharString>> &b = buckets[i];
+ Map<uint32_t, int> &t = table.write[i];
+
+ if (b.size() == 0) {
+ continue;
+ }
+
+ int d = 1;
+ int item = 0;
+
+ while (item < b.size()) {
+ uint32_t slot = hash(d, b[item].second.get_data());
+ if (t.has(slot)) {
+ item = 0;
+ d++;
+ t.clear();
+ } else {
+ t[slot] = b[item].first;
+ item++;
+ }
+ }
+
+ hfunc_table.write[i] = d;
+ bucket_table_size += 2 + b.size() * 4;
+ }
+
+ ERR_FAIL_COND(bucket_table_size == 0);
+
+ hash_table.resize(size);
+ bucket_table.resize(bucket_table_size);
+
+ int *htwb = hash_table.ptrw();
+ int *btwb = bucket_table.ptrw();
+
+ uint32_t *htw = (uint32_t *)&htwb[0];
+ uint32_t *btw = (uint32_t *)&btwb[0];
+
+ int btindex = 0;
+ int collisions = 0;
+
+ for (int i = 0; i < size; i++) {
+ const Map<uint32_t, int> &t = table[i];
+ if (t.size() == 0) {
+ htw[i] = 0xFFFFFFFF; //nothing
+ continue;
+ } else if (t.size() > 1) {
+ collisions += t.size() - 1;
+ }
+
+ htw[i] = btindex;
+ btw[btindex++] = t.size();
+ btw[btindex++] = hfunc_table[i];
+
+ for (Map<uint32_t, int>::Element *E = t.front(); E; E = E->next()) {
+ btw[btindex++] = E->key();
+ btw[btindex++] = compressed[E->get()].offset;
+ btw[btindex++] = compressed[E->get()].compressed.size();
+ btw[btindex++] = compressed[E->get()].orig_len;
+ }
+ }
+
+ strings.resize(total_compression_size);
+ uint8_t *cw = strings.ptrw();
+
+ for (int i = 0; i < compressed.size(); i++) {
+ memcpy(&cw[compressed[i].offset], compressed[i].compressed.get_data(), compressed[i].compressed.size());
+ }
+
+ ERR_FAIL_COND(btindex != bucket_table_size);
+ set_locale(p_from->get_locale());
+
+#endif
+}
+
+bool OptimizedTranslation::_set(const StringName &p_name, const Variant &p_value) {
+ String name = p_name.operator String();
+ if (name == "hash_table") {
+ hash_table = p_value;
+ } else if (name == "bucket_table") {
+ bucket_table = p_value;
+ } else if (name == "strings") {
+ strings = p_value;
+ } else if (name == "load_from") {
+ generate(p_value);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+bool OptimizedTranslation::_get(const StringName &p_name, Variant &r_ret) const {
+ String name = p_name.operator String();
+ if (name == "hash_table") {
+ r_ret = hash_table;
+ } else if (name == "bucket_table") {
+ r_ret = bucket_table;
+ } else if (name == "strings") {
+ r_ret = strings;
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+StringName OptimizedTranslation::get_message(const StringName &p_src_text, const StringName &p_context) const {
+ // p_context passed in is ignore. The use of context is not yet supported in OptimizedTranslation.
+
+ int htsize = hash_table.size();
+
+ if (htsize == 0) {
+ return StringName();
+ }
+
+ CharString str = p_src_text.operator String().utf8();
+ uint32_t h = hash(0, str.get_data());
+
+ const int *htr = hash_table.ptr();
+ const uint32_t *htptr = (const uint32_t *)&htr[0];
+ const int *btr = bucket_table.ptr();
+ const uint32_t *btptr = (const uint32_t *)&btr[0];
+ const uint8_t *sr = strings.ptr();
+ const char *sptr = (const char *)&sr[0];
+
+ uint32_t p = htptr[h % htsize];
+
+ if (p == 0xFFFFFFFF) {
+ return StringName(); //nothing
+ }
+
+ const Bucket &bucket = *(const Bucket *)&btptr[p];
+
+ h = hash(bucket.func, str.get_data());
+
+ int idx = -1;
+
+ for (int i = 0; i < bucket.size; i++) {
+ if (bucket.elem[i].key == h) {
+ idx = i;
+ break;
+ }
+ }
+
+ if (idx == -1) {
+ return StringName();
+ }
+
+ if (bucket.elem[idx].comp_size == bucket.elem[idx].uncomp_size) {
+ String rstr;
+ rstr.parse_utf8(&sptr[bucket.elem[idx].str_offset], bucket.elem[idx].uncomp_size);
+
+ return rstr;
+ } else {
+ CharString uncomp;
+ uncomp.resize(bucket.elem[idx].uncomp_size + 1);
+ smaz_decompress(&sptr[bucket.elem[idx].str_offset], bucket.elem[idx].comp_size, uncomp.ptrw(), bucket.elem[idx].uncomp_size);
+ String rstr;
+ rstr.parse_utf8(uncomp.get_data());
+ return rstr;
+ }
+}
+
+StringName OptimizedTranslation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
+ // The use of plurals translation is not yet supported in OptimizedTranslation.
+ return get_message(p_src_text, p_context);
+}
+
+void OptimizedTranslation::_get_property_list(List<PropertyInfo> *p_list) const {
+ p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "hash_table"));
+ p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "bucket_table"));
+ p_list->push_back(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "strings"));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "load_from", PROPERTY_HINT_RESOURCE_TYPE, "Translation", PROPERTY_USAGE_EDITOR));
+}
+
+void OptimizedTranslation::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("generate", "from"), &OptimizedTranslation::generate);
+}
diff --git a/core/string/optimized_translation.h b/core/string/optimized_translation.h
new file mode 100644
index 0000000000..bccf932383
--- /dev/null
+++ b/core/string/optimized_translation.h
@@ -0,0 +1,89 @@
+/*************************************************************************/
+/* optimized_translation.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef OPTIMIZED_TRANSLATION_H
+#define OPTIMIZED_TRANSLATION_H
+
+#include "core/string/translation.h"
+
+class OptimizedTranslation : public Translation {
+ GDCLASS(OptimizedTranslation, Translation);
+
+ //this translation uses a sort of modified perfect hash algorithm
+ //it requires hashing strings twice and then does a binary search,
+ //so it's slower, but at the same time it has an extreemly high chance
+ //of catching untranslated strings
+
+ //load/store friendly types
+ Vector<int> hash_table;
+ Vector<int> bucket_table;
+ Vector<uint8_t> strings;
+
+ struct Bucket {
+ int size;
+ uint32_t func;
+
+ struct Elem {
+ uint32_t key;
+ uint32_t str_offset;
+ uint32_t comp_size;
+ uint32_t uncomp_size;
+ };
+
+ Elem elem[1];
+ };
+
+ _FORCE_INLINE_ uint32_t hash(uint32_t d, const char *p_str) const {
+ if (d == 0) {
+ d = 0x1000193;
+ }
+ while (*p_str) {
+ d = (d * 0x1000193) ^ uint32_t(*p_str);
+ p_str++;
+ }
+
+ return d;
+ }
+
+protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+ static void _bind_methods();
+
+public:
+ virtual StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const override; //overridable for other implementations
+ virtual StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const override;
+ void generate(const Ref<Translation> &p_from);
+
+ OptimizedTranslation() {}
+};
+
+#endif // OPTIMIZED_TRANSLATION_H
diff --git a/core/string/print_string.cpp b/core/string/print_string.cpp
new file mode 100644
index 0000000000..345371d733
--- /dev/null
+++ b/core/string/print_string.cpp
@@ -0,0 +1,110 @@
+/*************************************************************************/
+/* print_string.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "print_string.h"
+
+#include "core/os/os.h"
+
+#include <stdio.h>
+
+static PrintHandlerList *print_handler_list = nullptr;
+bool _print_line_enabled = true;
+bool _print_error_enabled = true;
+
+void add_print_handler(PrintHandlerList *p_handler) {
+ _global_lock();
+ p_handler->next = print_handler_list;
+ print_handler_list = p_handler;
+ _global_unlock();
+}
+
+void remove_print_handler(PrintHandlerList *p_handler) {
+ _global_lock();
+
+ PrintHandlerList *prev = nullptr;
+ PrintHandlerList *l = print_handler_list;
+
+ while (l) {
+ if (l == p_handler) {
+ if (prev) {
+ prev->next = l->next;
+ } else {
+ print_handler_list = l->next;
+ }
+ break;
+ }
+ prev = l;
+ l = l->next;
+ }
+ //OS::get_singleton()->print("print handler list is %p\n",print_handler_list);
+
+ _global_unlock();
+ ERR_FAIL_COND(l == nullptr);
+}
+
+void print_line(String p_string) {
+ if (!_print_line_enabled) {
+ return;
+ }
+
+ OS::get_singleton()->print("%s\n", p_string.utf8().get_data());
+
+ _global_lock();
+ PrintHandlerList *l = print_handler_list;
+ while (l) {
+ l->printfunc(l->userdata, p_string, false);
+ l = l->next;
+ }
+
+ _global_unlock();
+}
+
+void print_error(String p_string) {
+ if (!_print_error_enabled) {
+ return;
+ }
+
+ OS::get_singleton()->printerr("%s\n", p_string.utf8().get_data());
+
+ _global_lock();
+ PrintHandlerList *l = print_handler_list;
+ while (l) {
+ l->printfunc(l->userdata, p_string, true);
+ l = l->next;
+ }
+
+ _global_unlock();
+}
+
+void print_verbose(String p_string) {
+ if (OS::get_singleton()->is_stdout_verbose()) {
+ print_line(p_string);
+ }
+}
diff --git a/core/string/print_string.h b/core/string/print_string.h
new file mode 100644
index 0000000000..1a9ff1efd6
--- /dev/null
+++ b/core/string/print_string.h
@@ -0,0 +1,58 @@
+/*************************************************************************/
+/* print_string.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef PRINT_STRING_H
+#define PRINT_STRING_H
+
+#include "core/string/ustring.h"
+
+extern void (*_print_func)(String);
+
+typedef void (*PrintHandlerFunc)(void *, const String &p_string, bool p_error);
+
+struct PrintHandlerList {
+ PrintHandlerFunc printfunc = nullptr;
+ void *userdata = nullptr;
+
+ PrintHandlerList *next = nullptr;
+
+ PrintHandlerList() {}
+};
+
+void add_print_handler(PrintHandlerList *p_handler);
+void remove_print_handler(PrintHandlerList *p_handler);
+
+extern bool _print_line_enabled;
+extern bool _print_error_enabled;
+extern void print_line(String p_string);
+extern void print_error(String p_string);
+extern void print_verbose(String p_string);
+
+#endif // PRINT_STRING_H
diff --git a/core/string/string_buffer.h b/core/string/string_buffer.h
new file mode 100644
index 0000000000..33897c3674
--- /dev/null
+++ b/core/string/string_buffer.h
@@ -0,0 +1,162 @@
+/*************************************************************************/
+/* string_buffer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef STRING_BUFFER_H
+#define STRING_BUFFER_H
+
+#include "core/string/ustring.h"
+
+template <int SHORT_BUFFER_SIZE = 64>
+class StringBuffer {
+ char32_t short_buffer[SHORT_BUFFER_SIZE];
+ String buffer;
+ int string_length = 0;
+
+ _FORCE_INLINE_ char32_t *current_buffer_ptr() {
+ return static_cast<String &>(buffer).is_empty() ? short_buffer : buffer.ptrw();
+ }
+
+public:
+ StringBuffer &append(char32_t p_char);
+ StringBuffer &append(const String &p_string);
+ StringBuffer &append(const char *p_str);
+ StringBuffer &append(const char32_t *p_str, int p_clip_to_len = -1);
+
+ _FORCE_INLINE_ void operator+=(char32_t p_char) {
+ append(p_char);
+ }
+
+ _FORCE_INLINE_ void operator+=(const String &p_string) {
+ append(p_string);
+ }
+
+ _FORCE_INLINE_ void operator+=(const char *p_str) {
+ append(p_str);
+ }
+
+ _FORCE_INLINE_ void operator+=(const char32_t *p_str) {
+ append(p_str);
+ }
+
+ StringBuffer &reserve(int p_size);
+
+ int length() const;
+
+ String as_string();
+
+ double as_double();
+ int64_t as_int();
+
+ _FORCE_INLINE_ operator String() {
+ return as_string();
+ }
+};
+
+template <int SHORT_BUFFER_SIZE>
+StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(char32_t p_char) {
+ reserve(string_length + 2);
+ current_buffer_ptr()[string_length++] = p_char;
+ return *this;
+}
+
+template <int SHORT_BUFFER_SIZE>
+StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const String &p_string) {
+ return append(p_string.get_data());
+}
+
+template <int SHORT_BUFFER_SIZE>
+StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const char *p_str) {
+ int len = strlen(p_str);
+ reserve(string_length + len + 1);
+
+ char32_t *buf = current_buffer_ptr();
+ for (const char *c_ptr = p_str; *c_ptr; ++c_ptr) {
+ buf[string_length++] = *c_ptr;
+ }
+ return *this;
+}
+
+template <int SHORT_BUFFER_SIZE>
+StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const char32_t *p_str, int p_clip_to_len) {
+ int len = 0;
+ while ((p_clip_to_len < 0 || len < p_clip_to_len) && p_str[len]) {
+ ++len;
+ }
+ reserve(string_length + len + 1);
+ memcpy(&(current_buffer_ptr()[string_length]), p_str, len * sizeof(char32_t));
+ string_length += len;
+
+ return *this;
+}
+
+template <int SHORT_BUFFER_SIZE>
+StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::reserve(int p_size) {
+ if (p_size < SHORT_BUFFER_SIZE || p_size < buffer.size()) {
+ return *this;
+ }
+
+ bool need_copy = string_length > 0 && buffer.is_empty();
+ buffer.resize(next_power_of_2(p_size));
+ if (need_copy) {
+ memcpy(buffer.ptrw(), short_buffer, string_length * sizeof(char32_t));
+ }
+
+ return *this;
+}
+
+template <int SHORT_BUFFER_SIZE>
+int StringBuffer<SHORT_BUFFER_SIZE>::length() const {
+ return string_length;
+}
+
+template <int SHORT_BUFFER_SIZE>
+String StringBuffer<SHORT_BUFFER_SIZE>::as_string() {
+ current_buffer_ptr()[string_length] = '\0';
+ if (buffer.is_empty()) {
+ return String(short_buffer);
+ } else {
+ buffer.resize(string_length + 1);
+ return buffer;
+ }
+}
+
+template <int SHORT_BUFFER_SIZE>
+double StringBuffer<SHORT_BUFFER_SIZE>::as_double() {
+ current_buffer_ptr()[string_length] = '\0';
+ return String::to_float(current_buffer_ptr());
+}
+
+template <int SHORT_BUFFER_SIZE>
+int64_t StringBuffer<SHORT_BUFFER_SIZE>::as_int() {
+ current_buffer_ptr()[string_length] = '\0';
+ return String::to_int(current_buffer_ptr());
+}
+
+#endif // STRING_BUFFER_H
diff --git a/core/string/string_builder.cpp b/core/string/string_builder.cpp
new file mode 100644
index 0000000000..834c87c845
--- /dev/null
+++ b/core/string/string_builder.cpp
@@ -0,0 +1,99 @@
+/*************************************************************************/
+/* string_builder.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "string_builder.h"
+
+#include <string.h>
+
+StringBuilder &StringBuilder::append(const String &p_string) {
+ if (p_string == String()) {
+ return *this;
+ }
+
+ strings.push_back(p_string);
+ appended_strings.push_back(-1);
+
+ string_length += p_string.length();
+
+ return *this;
+}
+
+StringBuilder &StringBuilder::append(const char *p_cstring) {
+ int32_t len = strlen(p_cstring);
+
+ c_strings.push_back(p_cstring);
+ appended_strings.push_back(len);
+
+ string_length += len;
+
+ return *this;
+}
+
+String StringBuilder::as_string() const {
+ if (string_length == 0) {
+ return "";
+ }
+
+ char32_t *buffer = memnew_arr(char32_t, string_length);
+
+ int current_position = 0;
+
+ int godot_string_elem = 0;
+ int c_string_elem = 0;
+
+ for (int i = 0; i < appended_strings.size(); i++) {
+ if (appended_strings[i] == -1) {
+ // Godot string
+ const String &s = strings[godot_string_elem];
+
+ memcpy(buffer + current_position, s.ptr(), s.length() * sizeof(char32_t));
+
+ current_position += s.length();
+
+ godot_string_elem++;
+ } else {
+ const char *s = c_strings[c_string_elem];
+
+ for (int32_t j = 0; j < appended_strings[i]; j++) {
+ buffer[current_position + j] = s[j];
+ }
+
+ current_position += appended_strings[i];
+
+ c_string_elem++;
+ }
+ }
+
+ String final_string = String(buffer, string_length);
+
+ memdelete_arr(buffer);
+
+ return final_string;
+}
diff --git a/core/string/string_builder.h b/core/string/string_builder.h
new file mode 100644
index 0000000000..30ce2a06f7
--- /dev/null
+++ b/core/string/string_builder.h
@@ -0,0 +1,84 @@
+/*************************************************************************/
+/* string_builder.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef STRING_BUILDER_H
+#define STRING_BUILDER_H
+
+#include "core/string/ustring.h"
+#include "core/templates/vector.h"
+
+class StringBuilder {
+ uint32_t string_length = 0;
+
+ Vector<String> strings;
+ Vector<const char *> c_strings;
+
+ // -1 means it's a Godot String
+ // a natural number means C string.
+ Vector<int32_t> appended_strings;
+
+public:
+ StringBuilder &append(const String &p_string);
+ StringBuilder &append(const char *p_cstring);
+
+ _FORCE_INLINE_ StringBuilder &operator+(const String &p_string) {
+ return append(p_string);
+ }
+
+ _FORCE_INLINE_ StringBuilder &operator+(const char *p_cstring) {
+ return append(p_cstring);
+ }
+
+ _FORCE_INLINE_ void operator+=(const String &p_string) {
+ append(p_string);
+ }
+
+ _FORCE_INLINE_ void operator+=(const char *p_cstring) {
+ append(p_cstring);
+ }
+
+ _FORCE_INLINE_ int num_strings_appended() const {
+ return appended_strings.size();
+ }
+
+ _FORCE_INLINE_ uint32_t get_string_length() const {
+ return string_length;
+ }
+
+ String as_string() const;
+
+ _FORCE_INLINE_ operator String() const {
+ return as_string();
+ }
+
+ StringBuilder() {}
+};
+
+#endif // STRING_BUILDER_H
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
new file mode 100644
index 0000000000..9024f60dae
--- /dev/null
+++ b/core/string/string_name.cpp
@@ -0,0 +1,479 @@
+/*************************************************************************/
+/* string_name.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "string_name.h"
+
+#include "core/os/os.h"
+#include "core/string/print_string.h"
+
+StaticCString StaticCString::create(const char *p_ptr) {
+ StaticCString scs;
+ scs.ptr = p_ptr;
+ return scs;
+}
+
+StringName::_Data *StringName::_table[STRING_TABLE_LEN];
+
+StringName _scs_create(const char *p_chr, bool p_static) {
+ return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName());
+}
+
+bool StringName::configured = false;
+Mutex StringName::mutex;
+
+#ifdef DEBUG_ENABLED
+bool StringName::debug_stringname = false;
+#endif
+
+void StringName::setup() {
+ ERR_FAIL_COND(configured);
+ for (int i = 0; i < STRING_TABLE_LEN; i++) {
+ _table[i] = nullptr;
+ }
+ configured = true;
+}
+
+void StringName::cleanup() {
+ MutexLock lock(mutex);
+
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ Vector<_Data *> data;
+ for (int i = 0; i < STRING_TABLE_LEN; i++) {
+ _Data *d = _table[i];
+ while (d) {
+ data.push_back(d);
+ d = d->next;
+ }
+ }
+ print_line("\nStringName Reference Ranking:\n");
+ data.sort_custom<DebugSortReferences>();
+ for (int i = 0; i < MIN(100, data.size()); i++) {
+ print_line(itos(i + 1) + ": " + data[i]->get_name() + " - " + itos(data[i]->debug_references));
+ }
+ }
+#endif
+ int lost_strings = 0;
+ for (int i = 0; i < STRING_TABLE_LEN; i++) {
+ while (_table[i]) {
+ _Data *d = _table[i];
+ lost_strings++;
+ if (d->static_count.get() != d->refcount.get() && OS::get_singleton()->is_stdout_verbose()) {
+ if (d->cname) {
+ print_line("Orphan StringName: " + String(d->cname));
+ } else {
+ print_line("Orphan StringName: " + String(d->name));
+ }
+ }
+
+ _table[i] = _table[i]->next;
+ memdelete(d);
+ }
+ }
+ if (lost_strings) {
+ print_verbose("StringName: " + itos(lost_strings) + " unclaimed string names at exit.");
+ }
+ configured = false;
+}
+
+void StringName::unref() {
+ ERR_FAIL_COND(!configured);
+
+ if (_data && _data->refcount.unref()) {
+ MutexLock lock(mutex);
+
+ if (_data->static_count.get() > 0) {
+ if (_data->cname) {
+ ERR_PRINT("BUG: Unreferenced static string to 0: " + String(_data->cname));
+ } else {
+ ERR_PRINT("BUG: Unreferenced static string to 0: " + String(_data->name));
+ }
+ }
+ if (_data->prev) {
+ _data->prev->next = _data->next;
+ } else {
+ if (_table[_data->idx] != _data) {
+ ERR_PRINT("BUG!");
+ }
+ _table[_data->idx] = _data->next;
+ }
+
+ if (_data->next) {
+ _data->next->prev = _data->prev;
+ }
+ memdelete(_data);
+ }
+
+ _data = nullptr;
+}
+
+bool StringName::operator==(const String &p_name) const {
+ if (!_data) {
+ return (p_name.length() == 0);
+ }
+
+ return (_data->get_name() == p_name);
+}
+
+bool StringName::operator==(const char *p_name) const {
+ if (!_data) {
+ return (p_name[0] == 0);
+ }
+
+ return (_data->get_name() == p_name);
+}
+
+bool StringName::operator!=(const String &p_name) const {
+ return !(operator==(p_name));
+}
+
+bool StringName::operator!=(const StringName &p_name) const {
+ // the real magic of all this mess happens here.
+ // this is why path comparisons are very fast
+ return _data != p_name._data;
+}
+
+void StringName::operator=(const StringName &p_name) {
+ if (this == &p_name) {
+ return;
+ }
+
+ unref();
+
+ if (p_name._data && p_name._data->refcount.ref()) {
+ _data = p_name._data;
+ }
+}
+
+StringName::StringName(const StringName &p_name) {
+ _data = nullptr;
+
+ ERR_FAIL_COND(!configured);
+
+ if (p_name._data && p_name._data->refcount.ref()) {
+ _data = p_name._data;
+ }
+}
+
+StringName::StringName(const char *p_name, bool p_static) {
+ _data = nullptr;
+
+ ERR_FAIL_COND(!configured);
+
+ if (!p_name || p_name[0] == 0) {
+ return; //empty, ignore
+ }
+
+ MutexLock lock(mutex);
+
+ uint32_t hash = String::hash(p_name);
+
+ uint32_t idx = hash & STRING_TABLE_MASK;
+
+ _data = _table[idx];
+
+ while (_data) {
+ // compare hash first
+ if (_data->hash == hash && _data->get_name() == p_name) {
+ break;
+ }
+ _data = _data->next;
+ }
+
+ if (_data) {
+ if (_data->refcount.ref()) {
+ // exists
+ if (p_static) {
+ _data->static_count.increment();
+ }
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
+ }
+#endif
+ }
+
+ return;
+ }
+
+ _data = memnew(_Data);
+ _data->name = p_name;
+ _data->refcount.init();
+ _data->static_count.set(p_static ? 1 : 0);
+ _data->hash = hash;
+ _data->idx = idx;
+ _data->cname = nullptr;
+ _data->next = _table[idx];
+ _data->prev = nullptr;
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ // Keep in memory, force static.
+ _data->refcount.ref();
+ _data->static_count.increment();
+ }
+#endif
+ if (_table[idx]) {
+ _table[idx]->prev = _data;
+ }
+ _table[idx] = _data;
+}
+
+StringName::StringName(const StaticCString &p_static_string, bool p_static) {
+ _data = nullptr;
+
+ ERR_FAIL_COND(!configured);
+
+ ERR_FAIL_COND(!p_static_string.ptr || !p_static_string.ptr[0]);
+
+ MutexLock lock(mutex);
+
+ uint32_t hash = String::hash(p_static_string.ptr);
+
+ uint32_t idx = hash & STRING_TABLE_MASK;
+
+ _data = _table[idx];
+
+ while (_data) {
+ // compare hash first
+ if (_data->hash == hash && _data->get_name() == p_static_string.ptr) {
+ break;
+ }
+ _data = _data->next;
+ }
+
+ if (_data) {
+ if (_data->refcount.ref()) {
+ // exists
+ if (p_static) {
+ _data->static_count.increment();
+ }
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
+ }
+#endif
+ return;
+ }
+ }
+
+ _data = memnew(_Data);
+
+ _data->refcount.init();
+ _data->static_count.set(p_static ? 1 : 0);
+ _data->hash = hash;
+ _data->idx = idx;
+ _data->cname = p_static_string.ptr;
+ _data->next = _table[idx];
+ _data->prev = nullptr;
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ // Keep in memory, force static.
+ _data->refcount.ref();
+ _data->static_count.increment();
+ }
+#endif
+ if (_table[idx]) {
+ _table[idx]->prev = _data;
+ }
+ _table[idx] = _data;
+}
+
+StringName::StringName(const String &p_name, bool p_static) {
+ _data = nullptr;
+
+ ERR_FAIL_COND(!configured);
+
+ if (p_name == String()) {
+ return;
+ }
+
+ MutexLock lock(mutex);
+
+ uint32_t hash = p_name.hash();
+ uint32_t idx = hash & STRING_TABLE_MASK;
+
+ _data = _table[idx];
+
+ while (_data) {
+ if (_data->hash == hash && _data->get_name() == p_name) {
+ break;
+ }
+ _data = _data->next;
+ }
+
+ if (_data) {
+ if (_data->refcount.ref()) {
+ // exists
+ if (p_static) {
+ _data->static_count.increment();
+ }
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
+ }
+#endif
+ return;
+ }
+ }
+
+ _data = memnew(_Data);
+ _data->name = p_name;
+ _data->refcount.init();
+ _data->static_count.set(p_static ? 1 : 0);
+ _data->hash = hash;
+ _data->idx = idx;
+ _data->cname = nullptr;
+ _data->next = _table[idx];
+ _data->prev = nullptr;
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ // Keep in memory, force static.
+ _data->refcount.ref();
+ _data->static_count.increment();
+ }
+#endif
+
+ if (_table[idx]) {
+ _table[idx]->prev = _data;
+ }
+ _table[idx] = _data;
+}
+
+StringName StringName::search(const char *p_name) {
+ ERR_FAIL_COND_V(!configured, StringName());
+
+ ERR_FAIL_COND_V(!p_name, StringName());
+ if (!p_name[0]) {
+ return StringName();
+ }
+
+ MutexLock lock(mutex);
+
+ uint32_t hash = String::hash(p_name);
+ uint32_t idx = hash & STRING_TABLE_MASK;
+
+ _Data *_data = _table[idx];
+
+ while (_data) {
+ // compare hash first
+ if (_data->hash == hash && _data->get_name() == p_name) {
+ break;
+ }
+ _data = _data->next;
+ }
+
+ if (_data && _data->refcount.ref()) {
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
+ }
+#endif
+
+ return StringName(_data);
+ }
+
+ return StringName(); //does not exist
+}
+
+StringName StringName::search(const char32_t *p_name) {
+ ERR_FAIL_COND_V(!configured, StringName());
+
+ ERR_FAIL_COND_V(!p_name, StringName());
+ if (!p_name[0]) {
+ return StringName();
+ }
+
+ MutexLock lock(mutex);
+
+ uint32_t hash = String::hash(p_name);
+
+ uint32_t idx = hash & STRING_TABLE_MASK;
+
+ _Data *_data = _table[idx];
+
+ while (_data) {
+ // compare hash first
+ if (_data->hash == hash && _data->get_name() == p_name) {
+ break;
+ }
+ _data = _data->next;
+ }
+
+ if (_data && _data->refcount.ref()) {
+ return StringName(_data);
+ }
+
+ return StringName(); //does not exist
+}
+
+StringName StringName::search(const String &p_name) {
+ ERR_FAIL_COND_V(p_name == "", StringName());
+
+ MutexLock lock(mutex);
+
+ uint32_t hash = p_name.hash();
+
+ uint32_t idx = hash & STRING_TABLE_MASK;
+
+ _Data *_data = _table[idx];
+
+ while (_data) {
+ // compare hash first
+ if (_data->hash == hash && p_name == _data->get_name()) {
+ break;
+ }
+ _data = _data->next;
+ }
+
+ if (_data && _data->refcount.ref()) {
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
+ }
+#endif
+ return StringName(_data);
+ }
+
+ return StringName(); //does not exist
+}
+
+bool operator==(const String &p_name, const StringName &p_string_name) {
+ return p_name == p_string_name.operator String();
+}
+bool operator!=(const String &p_name, const StringName &p_string_name) {
+ return p_name != p_string_name.operator String();
+}
+
+bool operator==(const char *p_name, const StringName &p_string_name) {
+ return p_name == p_string_name.operator String();
+}
+bool operator!=(const char *p_name, const StringName &p_string_name) {
+ return p_name != p_string_name.operator String();
+}
diff --git a/core/string/string_name.h b/core/string/string_name.h
new file mode 100644
index 0000000000..ce7988744b
--- /dev/null
+++ b/core/string/string_name.h
@@ -0,0 +1,186 @@
+/*************************************************************************/
+/* string_name.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef STRING_NAME_H
+#define STRING_NAME_H
+
+#include "core/os/mutex.h"
+#include "core/string/ustring.h"
+#include "core/templates/safe_refcount.h"
+
+class Main;
+
+struct StaticCString {
+ const char *ptr;
+ static StaticCString create(const char *p_ptr);
+};
+
+class StringName {
+ enum {
+ STRING_TABLE_BITS = 16,
+ STRING_TABLE_LEN = 1 << STRING_TABLE_BITS,
+ STRING_TABLE_MASK = STRING_TABLE_LEN - 1
+ };
+
+ struct _Data {
+ SafeRefCount refcount;
+ SafeNumeric<uint32_t> static_count;
+ const char *cname = nullptr;
+ String name;
+#ifdef DEBUG_ENABLED
+ uint32_t debug_references = 0;
+#endif
+ String get_name() const { return cname ? String(cname) : name; }
+ int idx = 0;
+ uint32_t hash = 0;
+ _Data *prev = nullptr;
+ _Data *next = nullptr;
+ _Data() {}
+ };
+
+ static _Data *_table[STRING_TABLE_LEN];
+
+ _Data *_data = nullptr;
+
+ union _HashUnion {
+ _Data *ptr;
+ uint32_t hash;
+ };
+
+ void unref();
+ friend void register_core_types();
+ friend void unregister_core_types();
+ friend class Main;
+ static Mutex mutex;
+ static void setup();
+ static void cleanup();
+ static bool configured;
+#ifdef DEBUG_ENABLED
+ struct DebugSortReferences {
+ bool operator()(const _Data *p_left, const _Data *p_right) const {
+ return p_left->debug_references > p_right->debug_references;
+ }
+ };
+
+ static bool debug_stringname;
+#endif
+
+ StringName(_Data *p_data) { _data = p_data; }
+
+public:
+ operator const void *() const { return (_data && (_data->cname || !_data->name.is_empty())) ? (void *)1 : nullptr; }
+
+ bool operator==(const String &p_name) const;
+ bool operator==(const char *p_name) const;
+ bool operator!=(const String &p_name) const;
+ _FORCE_INLINE_ bool operator<(const StringName &p_name) const {
+ return _data < p_name._data;
+ }
+ _FORCE_INLINE_ bool operator==(const StringName &p_name) const {
+ // the real magic of all this mess happens here.
+ // this is why path comparisons are very fast
+ return _data == p_name._data;
+ }
+ _FORCE_INLINE_ uint32_t hash() const {
+ if (_data) {
+ return _data->hash;
+ } else {
+ return 0;
+ }
+ }
+ _FORCE_INLINE_ const void *data_unique_pointer() const {
+ return (void *)_data;
+ }
+ bool operator!=(const StringName &p_name) const;
+
+ _FORCE_INLINE_ operator String() const {
+ if (_data) {
+ if (_data->cname) {
+ return String(_data->cname);
+ } else {
+ return _data->name;
+ }
+ }
+
+ return String();
+ }
+
+ static StringName search(const char *p_name);
+ static StringName search(const char32_t *p_name);
+ static StringName search(const String &p_name);
+
+ struct AlphCompare {
+ _FORCE_INLINE_ bool operator()(const StringName &l, const StringName &r) const {
+ const char *l_cname = l._data ? l._data->cname : "";
+ const char *r_cname = r._data ? r._data->cname : "";
+
+ if (l_cname) {
+ if (r_cname) {
+ return is_str_less(l_cname, r_cname);
+ } else {
+ return is_str_less(l_cname, r._data->name.ptr());
+ }
+ } else {
+ if (r_cname) {
+ return is_str_less(l._data->name.ptr(), r_cname);
+ } else {
+ return is_str_less(l._data->name.ptr(), r._data->name.ptr());
+ }
+ }
+ }
+ };
+
+ void operator=(const StringName &p_name);
+ StringName(const char *p_name, bool p_static = false);
+ StringName(const StringName &p_name);
+ StringName(const String &p_name, bool p_static = false);
+ StringName(const StaticCString &p_static_string, bool p_static = false);
+ StringName() {}
+ _FORCE_INLINE_ ~StringName() {
+ if (likely(configured) && _data) { //only free if configured
+ unref();
+ }
+ }
+
+#ifdef DEBUG_ENABLED
+ static void set_debug_stringnames(bool p_enable) { debug_stringname = p_enable; }
+#endif
+};
+
+bool operator==(const String &p_name, const StringName &p_string_name);
+bool operator!=(const String &p_name, const StringName &p_string_name);
+bool operator==(const char *p_name, const StringName &p_string_name);
+bool operator!=(const char *p_name, const StringName &p_string_name);
+
+StringName _scs_create(const char *p_chr, bool p_static = false);
+
+#define SNAME(m_arg) ([]() -> const StringName & { static StringName sname = _scs_create(m_arg, true); return sname; })()
+
+#endif // STRING_NAME_H
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
new file mode 100644
index 0000000000..cb7d924556
--- /dev/null
+++ b/core/string/translation.cpp
@@ -0,0 +1,1595 @@
+/*************************************************************************/
+/* translation.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "translation.h"
+
+#include "core/config/project_settings.h"
+#include "core/io/resource_loader.h"
+#include "core/os/os.h"
+
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#include "main/main.h"
+#endif
+
+// ISO 639-1 language codes (and a couple of three-letter ISO 639-2 codes),
+// with the addition of glibc locales with their regional identifiers.
+// This list must match the language names (in English) of locale_names.
+//
+// References:
+// - https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+// - https://lh.2xlibre.net/locales/
+// - https://iso639-3.sil.org/
+
+static const char *locale_list[] = {
+ "aa", // Afar
+ "aa_DJ", // Afar (Djibouti)
+ "aa_ER", // Afar (Eritrea)
+ "aa_ET", // Afar (Ethiopia)
+ "af", // Afrikaans
+ "af_ZA", // Afrikaans (South Africa)
+ "agr_PE", // Aguaruna (Peru)
+ "ak_GH", // Akan (Ghana)
+ "am_ET", // Amharic (Ethiopia)
+ "an_ES", // Aragonese (Spain)
+ "anp_IN", // Angika (India)
+ "ar", // Arabic
+ "ar_AE", // Arabic (United Arab Emirates)
+ "ar_BH", // Arabic (Bahrain)
+ "ar_DZ", // Arabic (Algeria)
+ "ar_EG", // Arabic (Egypt)
+ "ar_IN", // Arabic (India)
+ "ar_IQ", // Arabic (Iraq)
+ "ar_JO", // Arabic (Jordan)
+ "ar_KW", // Arabic (Kuwait)
+ "ar_LB", // Arabic (Lebanon)
+ "ar_LY", // Arabic (Libya)
+ "ar_MA", // Arabic (Morocco)
+ "ar_OM", // Arabic (Oman)
+ "ar_QA", // Arabic (Qatar)
+ "ar_SA", // Arabic (Saudi Arabia)
+ "ar_SD", // Arabic (Sudan)
+ "ar_SS", // Arabic (South Soudan)
+ "ar_SY", // Arabic (Syria)
+ "ar_TN", // Arabic (Tunisia)
+ "ar_YE", // Arabic (Yemen)
+ "as_IN", // Assamese (India)
+ "ast_ES", // Asturian (Spain)
+ "ayc_PE", // Southern Aymara (Peru)
+ "ay_PE", // Aymara (Peru)
+ "az", // Azerbaijani
+ "az_AZ", // Azerbaijani (Azerbaijan)
+ "be", // Belarusian
+ "be_BY", // Belarusian (Belarus)
+ "bem_ZM", // Bemba (Zambia)
+ "ber_DZ", // Berber languages (Algeria)
+ "ber_MA", // Berber languages (Morocco)
+ "bg", // Bulgarian
+ "bg_BG", // Bulgarian (Bulgaria)
+ "bhb_IN", // Bhili (India)
+ "bho_IN", // Bhojpuri (India)
+ "bi_TV", // Bislama (Tuvalu)
+ "bn", // Bengali
+ "bn_BD", // Bengali (Bangladesh)
+ "bn_IN", // Bengali (India)
+ "bo", // Tibetan
+ "bo_CN", // Tibetan (China)
+ "bo_IN", // Tibetan (India)
+ "br", // Breton
+ "br_FR", // Breton (France)
+ "brx_IN", // Bodo (India)
+ "bs_BA", // Bosnian (Bosnia and Herzegovina)
+ "byn_ER", // Bilin (Eritrea)
+ "ca", // Catalan
+ "ca_AD", // Catalan (Andorra)
+ "ca_ES", // Catalan (Spain)
+ "ca_FR", // Catalan (France)
+ "ca_IT", // Catalan (Italy)
+ "ce_RU", // Chechen (Russia)
+ "chr_US", // Cherokee (United States)
+ "cmn_TW", // Mandarin Chinese (Taiwan)
+ "crh_UA", // Crimean Tatar (Ukraine)
+ "csb_PL", // Kashubian (Poland)
+ "cs", // Czech
+ "cs_CZ", // Czech (Czech Republic)
+ "cv_RU", // Chuvash (Russia)
+ "cy_GB", // Welsh (United Kingdom)
+ "da", // Danish
+ "da_DK", // Danish (Denmark)
+ "de", // German
+ "de_AT", // German (Austria)
+ "de_BE", // German (Belgium)
+ "de_CH", // German (Switzerland)
+ "de_DE", // German (Germany)
+ "de_IT", // German (Italy)
+ "de_LU", // German (Luxembourg)
+ "doi_IN", // Dogri (India)
+ "dv_MV", // Dhivehi (Maldives)
+ "dz_BT", // Dzongkha (Bhutan)
+ "el", // Greek
+ "el_CY", // Greek (Cyprus)
+ "el_GR", // Greek (Greece)
+ "en", // English
+ "en_AG", // English (Antigua and Barbuda)
+ "en_AU", // English (Australia)
+ "en_BW", // English (Botswana)
+ "en_CA", // English (Canada)
+ "en_DK", // English (Denmark)
+ "en_GB", // English (United Kingdom)
+ "en_HK", // English (Hong Kong)
+ "en_IE", // English (Ireland)
+ "en_IL", // English (Israel)
+ "en_IN", // English (India)
+ "en_NG", // English (Nigeria)
+ "en_NZ", // English (New Zealand)
+ "en_PH", // English (Philippines)
+ "en_SG", // English (Singapore)
+ "en_US", // English (United States)
+ "en_ZA", // English (South Africa)
+ "en_ZM", // English (Zambia)
+ "en_ZW", // English (Zimbabwe)
+ "eo", // Esperanto
+ "es", // Spanish
+ "es_AR", // Spanish (Argentina)
+ "es_BO", // Spanish (Bolivia)
+ "es_CL", // Spanish (Chile)
+ "es_CO", // Spanish (Colombia)
+ "es_CR", // Spanish (Costa Rica)
+ "es_CU", // Spanish (Cuba)
+ "es_DO", // Spanish (Dominican Republic)
+ "es_EC", // Spanish (Ecuador)
+ "es_ES", // Spanish (Spain)
+ "es_GT", // Spanish (Guatemala)
+ "es_HN", // Spanish (Honduras)
+ "es_MX", // Spanish (Mexico)
+ "es_NI", // Spanish (Nicaragua)
+ "es_PA", // Spanish (Panama)
+ "es_PE", // Spanish (Peru)
+ "es_PR", // Spanish (Puerto Rico)
+ "es_PY", // Spanish (Paraguay)
+ "es_SV", // Spanish (El Salvador)
+ "es_US", // Spanish (United States)
+ "es_UY", // Spanish (Uruguay)
+ "es_VE", // Spanish (Venezuela)
+ "et", // Estonian
+ "et_EE", // Estonian (Estonia)
+ "eu", // Basque
+ "eu_ES", // Basque (Spain)
+ "fa", // Persian
+ "fa_IR", // Persian (Iran)
+ "ff_SN", // Fulah (Senegal)
+ "fi", // Finnish
+ "fi_FI", // Finnish (Finland)
+ "fil", // Filipino
+ "fil_PH", // Filipino (Philippines)
+ "fo_FO", // Faroese (Faroe Islands)
+ "fr", // French
+ "fr_BE", // French (Belgium)
+ "fr_CA", // French (Canada)
+ "fr_CH", // French (Switzerland)
+ "fr_FR", // French (France)
+ "fr_LU", // French (Luxembourg)
+ "fur_IT", // Friulian (Italy)
+ "fy_DE", // Western Frisian (Germany)
+ "fy_NL", // Western Frisian (Netherlands)
+ "ga", // Irish
+ "ga_IE", // Irish (Ireland)
+ "gd_GB", // Scottish Gaelic (United Kingdom)
+ "gez_ER", // Geez (Eritrea)
+ "gez_ET", // Geez (Ethiopia)
+ "gl", // Galician
+ "gl_ES", // Galician (Spain)
+ "gu_IN", // Gujarati (India)
+ "gv_GB", // Manx (United Kingdom)
+ "hak_TW", // Hakka Chinese (Taiwan)
+ "ha_NG", // Hausa (Nigeria)
+ "he", // Hebrew
+ "he_IL", // Hebrew (Israel)
+ "hi", // Hindi
+ "hi_IN", // Hindi (India)
+ "hne_IN", // Chhattisgarhi (India)
+ "hr", // Croatian
+ "hr_HR", // Croatian (Croatia)
+ "hsb_DE", // Upper Sorbian (Germany)
+ "ht_HT", // Haitian (Haiti)
+ "hu", // Hungarian
+ "hu_HU", // Hungarian (Hungary)
+ "hus_MX", // Huastec (Mexico)
+ "hy_AM", // Armenian (Armenia)
+ "ia_FR", // Interlingua (France)
+ "id", // Indonesian
+ "id_ID", // Indonesian (Indonesia)
+ "ig_NG", // Igbo (Nigeria)
+ "ik_CA", // Inupiaq (Canada)
+ "is", // Icelandic
+ "is_IS", // Icelandic (Iceland)
+ "it", // Italian
+ "it_CH", // Italian (Switzerland)
+ "it_IT", // Italian (Italy)
+ "iu_CA", // Inuktitut (Canada)
+ "ja", // Japanese
+ "ja_JP", // Japanese (Japan)
+ "kab_DZ", // Kabyle (Algeria)
+ "ka", // Georgian
+ "ka_GE", // Georgian (Georgia)
+ "kk_KZ", // Kazakh (Kazakhstan)
+ "kl_GL", // Kalaallisut (Greenland)
+ "km", // Central Khmer
+ "km_KH", // Central Khmer (Cambodia)
+ "kn_IN", // Kannada (India)
+ "kok_IN", // Konkani (India)
+ "ko", // Korean
+ "ko_KR", // Korean (South Korea)
+ "ks_IN", // Kashmiri (India)
+ "ku", // Kurdish
+ "ku_TR", // Kurdish (Turkey)
+ "kw_GB", // Cornish (United Kingdom)
+ "ky_KG", // Kirghiz (Kyrgyzstan)
+ "lb_LU", // Luxembourgish (Luxembourg)
+ "lg_UG", // Ganda (Uganda)
+ "li_BE", // Limburgan (Belgium)
+ "li_NL", // Limburgan (Netherlands)
+ "lij_IT", // Ligurian (Italy)
+ "ln_CD", // Lingala (Congo)
+ "lo_LA", // Lao (Laos)
+ "lt", // Lithuanian
+ "lt_LT", // Lithuanian (Lithuania)
+ "lv", // Latvian
+ "lv_LV", // Latvian (Latvia)
+ "lzh_TW", // Literary Chinese (Taiwan)
+ "mag_IN", // Magahi (India)
+ "mai_IN", // Maithili (India)
+ "mg_MG", // Malagasy (Madagascar)
+ "mh_MH", // Marshallese (Marshall Islands)
+ "mhr_RU", // Eastern Mari (Russia)
+ "mi", // Māori
+ "mi_NZ", // Māori (New Zealand)
+ "miq_NI", // Mískito (Nicaragua)
+ "mk", // Macedonian
+ "mk_MK", // Macedonian (Macedonia)
+ "ml", // Malayalam
+ "ml_IN", // Malayalam (India)
+ "mni_IN", // Manipuri (India)
+ "mn_MN", // Mongolian (Mongolia)
+ "mr", // Marathi
+ "mr_IN", // Marathi (India)
+ "ms", // Malay
+ "ms_MY", // Malay (Malaysia)
+ "mt", // Maltese
+ "mt_MT", // Maltese (Malta)
+ "my_MM", // Burmese (Myanmar)
+ "myv_RU", // Erzya (Russia)
+ "nah_MX", // Nahuatl languages (Mexico)
+ "nan_TW", // Min Nan Chinese (Taiwan)
+ "nb", // Norwegian Bokmål
+ "nb_NO", // Norwegian Bokmål (Norway)
+ "nds_DE", // Low German (Germany)
+ "nds_NL", // Low German (Netherlands)
+ "ne_NP", // Nepali (Nepal)
+ "nhn_MX", // Central Nahuatl (Mexico)
+ "niu_NU", // Niuean (Niue)
+ "niu_NZ", // Niuean (New Zealand)
+ "nl", // Dutch
+ "nl_AW", // Dutch (Aruba)
+ "nl_BE", // Dutch (Belgium)
+ "nl_NL", // Dutch (Netherlands)
+ "nn", // Norwegian Nynorsk
+ "nn_NO", // Norwegian Nynorsk (Norway)
+ "nr_ZA", // South Ndebele (South Africa)
+ "nso_ZA", // Pedi (South Africa)
+ "oc_FR", // Occitan (France)
+ "om", // Oromo
+ "om_ET", // Oromo (Ethiopia)
+ "om_KE", // Oromo (Kenya)
+ "or", // Oriya
+ "or_IN", // Oriya (India)
+ "os_RU", // Ossetian (Russia)
+ "pa_IN", // Panjabi (India)
+ "pap", // Papiamento
+ "pap_AN", // Papiamento (Netherlands Antilles)
+ "pap_AW", // Papiamento (Aruba)
+ "pap_CW", // Papiamento (Curaçao)
+ "pa_PK", // Panjabi (Pakistan)
+ "pl", // Polish
+ "pl_PL", // Polish (Poland)
+ "pr", // Pirate
+ "ps_AF", // Pushto (Afghanistan)
+ "pt", // Portuguese
+ "pt_BR", // Portuguese (Brazil)
+ "pt_PT", // Portuguese (Portugal)
+ "quy_PE", // Ayacucho Quechua (Peru)
+ "quz_PE", // Cusco Quechua (Peru)
+ "raj_IN", // Rajasthani (India)
+ "ro", // Romanian
+ "ro_RO", // Romanian (Romania)
+ "ru", // Russian
+ "ru_RU", // Russian (Russia)
+ "ru_UA", // Russian (Ukraine)
+ "rw_RW", // Kinyarwanda (Rwanda)
+ "sa_IN", // Sanskrit (India)
+ "sat_IN", // Santali (India)
+ "sc_IT", // Sardinian (Italy)
+ "sco", // Scots
+ "sd_IN", // Sindhi (India)
+ "se_NO", // Northern Sami (Norway)
+ "sgs_LT", // Samogitian (Lithuania)
+ "shs_CA", // Shuswap (Canada)
+ "sid_ET", // Sidamo (Ethiopia)
+ "si", // Sinhala
+ "si_LK", // Sinhala (Sri Lanka)
+ "sk", // Slovak
+ "sk_SK", // Slovak (Slovakia)
+ "sl", // Slovenian
+ "sl_SI", // Slovenian (Slovenia)
+ "so", // Somali
+ "so_DJ", // Somali (Djibouti)
+ "so_ET", // Somali (Ethiopia)
+ "so_KE", // Somali (Kenya)
+ "so_SO", // Somali (Somalia)
+ "son_ML", // Songhai languages (Mali)
+ "sq", // Albanian
+ "sq_AL", // Albanian (Albania)
+ "sq_KV", // Albanian (Kosovo)
+ "sq_MK", // Albanian (Macedonia)
+ "sr", // Serbian
+ "sr_Cyrl", // Serbian (Cyrillic)
+ "sr_Latn", // Serbian (Latin)
+ "sr_ME", // Serbian (Montenegro)
+ "sr_RS", // Serbian (Serbia)
+ "ss_ZA", // Swati (South Africa)
+ "st_ZA", // Southern Sotho (South Africa)
+ "sv", // Swedish
+ "sv_FI", // Swedish (Finland)
+ "sv_SE", // Swedish (Sweden)
+ "sw_KE", // Swahili (Kenya)
+ "sw_TZ", // Swahili (Tanzania)
+ "szl_PL", // Silesian (Poland)
+ "ta", // Tamil
+ "ta_IN", // Tamil (India)
+ "ta_LK", // Tamil (Sri Lanka)
+ "tcy_IN", // Tulu (India)
+ "te", // Telugu
+ "te_IN", // Telugu (India)
+ "tg_TJ", // Tajik (Tajikistan)
+ "the_NP", // Chitwania Tharu (Nepal)
+ "th", // Thai
+ "th_TH", // Thai (Thailand)
+ "ti", // Tigrinya
+ "ti_ER", // Tigrinya (Eritrea)
+ "ti_ET", // Tigrinya (Ethiopia)
+ "tig_ER", // Tigre (Eritrea)
+ "tk_TM", // Turkmen (Turkmenistan)
+ "tl_PH", // Tagalog (Philippines)
+ "tn_ZA", // Tswana (South Africa)
+ "tr", // Turkish
+ "tr_CY", // Turkish (Cyprus)
+ "tr_TR", // Turkish (Turkey)
+ "ts_ZA", // Tsonga (South Africa)
+ "tt", // Tatar
+ "tt_RU", // Tatar (Russia)
+ "tzm", // Central Atlas Tamazight
+ "tzm_MA", // Central Atlas Tamazight (Marrocos)
+ "ug_CN", // Uighur (China)
+ "uk", // Ukrainian
+ "uk_UA", // Ukrainian (Ukraine)
+ "unm_US", // Unami (United States)
+ "ur", // Urdu
+ "ur_IN", // Urdu (India)
+ "ur_PK", // Urdu (Pakistan)
+ "uz", // Uzbek
+ "uz_UZ", // Uzbek (Uzbekistan)
+ "ve_ZA", // Venda (South Africa)
+ "vi", // Vietnamese
+ "vi_VN", // Vietnamese (Vietnam)
+ "wa_BE", // Walloon (Belgium)
+ "wae_CH", // Walser (Switzerland)
+ "wal_ET", // Wolaytta (Ethiopia)
+ "wo_SN", // Wolof (Senegal)
+ "xh_ZA", // Xhosa (South Africa)
+ "yi_US", // Yiddish (United States)
+ "yo_NG", // Yoruba (Nigeria)
+ "yue_HK", // Yue Chinese (Hong Kong)
+ "zh", // Chinese
+ "zh_CN", // Chinese (China)
+ "zh_HK", // Chinese (Hong Kong)
+ "zh_SG", // Chinese (Singapore)
+ "zh_TW", // Chinese (Taiwan)
+ "zu_ZA", // Zulu (South Africa)
+ nullptr
+};
+
+static const char *locale_names[] = {
+ "Afar",
+ "Afar (Djibouti)",
+ "Afar (Eritrea)",
+ "Afar (Ethiopia)",
+ "Afrikaans",
+ "Afrikaans (South Africa)",
+ "Aguaruna (Peru)",
+ "Akan (Ghana)",
+ "Amharic (Ethiopia)",
+ "Aragonese (Spain)",
+ "Angika (India)",
+ "Arabic",
+ "Arabic (United Arab Emirates)",
+ "Arabic (Bahrain)",
+ "Arabic (Algeria)",
+ "Arabic (Egypt)",
+ "Arabic (India)",
+ "Arabic (Iraq)",
+ "Arabic (Jordan)",
+ "Arabic (Kuwait)",
+ "Arabic (Lebanon)",
+ "Arabic (Libya)",
+ "Arabic (Morocco)",
+ "Arabic (Oman)",
+ "Arabic (Qatar)",
+ "Arabic (Saudi Arabia)",
+ "Arabic (Sudan)",
+ "Arabic (South Soudan)",
+ "Arabic (Syria)",
+ "Arabic (Tunisia)",
+ "Arabic (Yemen)",
+ "Assamese (India)",
+ "Asturian (Spain)",
+ "Southern Aymara (Peru)",
+ "Aymara (Peru)",
+ "Azerbaijani",
+ "Azerbaijani (Azerbaijan)",
+ "Belarusian",
+ "Belarusian (Belarus)",
+ "Bemba (Zambia)",
+ "Berber languages (Algeria)",
+ "Berber languages (Morocco)",
+ "Bulgarian",
+ "Bulgarian (Bulgaria)",
+ "Bhili (India)",
+ "Bhojpuri (India)",
+ "Bislama (Tuvalu)",
+ "Bengali",
+ "Bengali (Bangladesh)",
+ "Bengali (India)",
+ "Tibetan",
+ "Tibetan (China)",
+ "Tibetan (India)",
+ "Breton",
+ "Breton (France)",
+ "Bodo (India)",
+ "Bosnian (Bosnia and Herzegovina)",
+ "Bilin (Eritrea)",
+ "Catalan",
+ "Catalan (Andorra)",
+ "Catalan (Spain)",
+ "Catalan (France)",
+ "Catalan (Italy)",
+ "Chechen (Russia)",
+ "Cherokee (United States)",
+ "Mandarin Chinese (Taiwan)",
+ "Crimean Tatar (Ukraine)",
+ "Kashubian (Poland)",
+ "Czech",
+ "Czech (Czech Republic)",
+ "Chuvash (Russia)",
+ "Welsh (United Kingdom)",
+ "Danish",
+ "Danish (Denmark)",
+ "German",
+ "German (Austria)",
+ "German (Belgium)",
+ "German (Switzerland)",
+ "German (Germany)",
+ "German (Italy)",
+ "German (Luxembourg)",
+ "Dogri (India)",
+ "Dhivehi (Maldives)",
+ "Dzongkha (Bhutan)",
+ "Greek",
+ "Greek (Cyprus)",
+ "Greek (Greece)",
+ "English",
+ "English (Antigua and Barbuda)",
+ "English (Australia)",
+ "English (Botswana)",
+ "English (Canada)",
+ "English (Denmark)",
+ "English (United Kingdom)",
+ "English (Hong Kong)",
+ "English (Ireland)",
+ "English (Israel)",
+ "English (India)",
+ "English (Nigeria)",
+ "English (New Zealand)",
+ "English (Philippines)",
+ "English (Singapore)",
+ "English (United States)",
+ "English (South Africa)",
+ "English (Zambia)",
+ "English (Zimbabwe)",
+ "Esperanto",
+ "Spanish",
+ "Spanish (Argentina)",
+ "Spanish (Bolivia)",
+ "Spanish (Chile)",
+ "Spanish (Colombia)",
+ "Spanish (Costa Rica)",
+ "Spanish (Cuba)",
+ "Spanish (Dominican Republic)",
+ "Spanish (Ecuador)",
+ "Spanish (Spain)",
+ "Spanish (Guatemala)",
+ "Spanish (Honduras)",
+ "Spanish (Mexico)",
+ "Spanish (Nicaragua)",
+ "Spanish (Panama)",
+ "Spanish (Peru)",
+ "Spanish (Puerto Rico)",
+ "Spanish (Paraguay)",
+ "Spanish (El Salvador)",
+ "Spanish (United States)",
+ "Spanish (Uruguay)",
+ "Spanish (Venezuela)",
+ "Estonian",
+ "Estonian (Estonia)",
+ "Basque",
+ "Basque (Spain)",
+ "Persian",
+ "Persian (Iran)",
+ "Fulah (Senegal)",
+ "Finnish",
+ "Finnish (Finland)",
+ "Filipino",
+ "Filipino (Philippines)",
+ "Faroese (Faroe Islands)",
+ "French",
+ "French (Belgium)",
+ "French (Canada)",
+ "French (Switzerland)",
+ "French (France)",
+ "French (Luxembourg)",
+ "Friulian (Italy)",
+ "Western Frisian (Germany)",
+ "Western Frisian (Netherlands)",
+ "Irish",
+ "Irish (Ireland)",
+ "Scottish Gaelic (United Kingdom)",
+ "Geez (Eritrea)",
+ "Geez (Ethiopia)",
+ "Galician",
+ "Galician (Spain)",
+ "Gujarati (India)",
+ "Manx (United Kingdom)",
+ "Hakka Chinese (Taiwan)",
+ "Hausa (Nigeria)",
+ "Hebrew",
+ "Hebrew (Israel)",
+ "Hindi",
+ "Hindi (India)",
+ "Chhattisgarhi (India)",
+ "Croatian",
+ "Croatian (Croatia)",
+ "Upper Sorbian (Germany)",
+ "Haitian (Haiti)",
+ "Hungarian",
+ "Hungarian (Hungary)",
+ "Huastec (Mexico)",
+ "Armenian (Armenia)",
+ "Interlingua (France)",
+ "Indonesian",
+ "Indonesian (Indonesia)",
+ "Igbo (Nigeria)",
+ "Inupiaq (Canada)",
+ "Icelandic",
+ "Icelandic (Iceland)",
+ "Italian",
+ "Italian (Switzerland)",
+ "Italian (Italy)",
+ "Inuktitut (Canada)",
+ "Japanese",
+ "Japanese (Japan)",
+ "Kabyle (Algeria)",
+ "Georgian",
+ "Georgian (Georgia)",
+ "Kazakh (Kazakhstan)",
+ "Kalaallisut (Greenland)",
+ "Central Khmer",
+ "Central Khmer (Cambodia)",
+ "Kannada (India)",
+ "Konkani (India)",
+ "Korean",
+ "Korean (South Korea)",
+ "Kashmiri (India)",
+ "Kurdish",
+ "Kurdish (Turkey)",
+ "Cornish (United Kingdom)",
+ "Kirghiz (Kyrgyzstan)",
+ "Luxembourgish (Luxembourg)",
+ "Ganda (Uganda)",
+ "Limburgan (Belgium)",
+ "Limburgan (Netherlands)",
+ "Ligurian (Italy)",
+ "Lingala (Congo)",
+ "Lao (Laos)",
+ "Lithuanian",
+ "Lithuanian (Lithuania)",
+ "Latvian",
+ "Latvian (Latvia)",
+ "Literary Chinese (Taiwan)",
+ "Magahi (India)",
+ "Maithili (India)",
+ "Malagasy (Madagascar)",
+ "Marshallese (Marshall Islands)",
+ "Eastern Mari (Russia)",
+ "Māori",
+ "Māori (New Zealand)",
+ "Mískito (Nicaragua)",
+ "Macedonian",
+ "Macedonian (Macedonia)",
+ "Malayalam",
+ "Malayalam (India)",
+ "Manipuri (India)",
+ "Mongolian (Mongolia)",
+ "Marathi",
+ "Marathi (India)",
+ "Malay",
+ "Malay (Malaysia)",
+ "Maltese",
+ "Maltese (Malta)",
+ "Burmese (Myanmar)",
+ "Erzya (Russia)",
+ "Nahuatl languages (Mexico)",
+ "Min Nan Chinese (Taiwan)",
+ "Norwegian Bokmål",
+ "Norwegian Bokmål (Norway)",
+ "Low German (Germany)",
+ "Low German (Netherlands)",
+ "Nepali (Nepal)",
+ "Central Nahuatl (Mexico)",
+ "Niuean (Niue)",
+ "Niuean (New Zealand)",
+ "Dutch",
+ "Dutch (Aruba)",
+ "Dutch (Belgium)",
+ "Dutch (Netherlands)",
+ "Norwegian Nynorsk",
+ "Norwegian Nynorsk (Norway)",
+ "South Ndebele (South Africa)",
+ "Pedi (South Africa)",
+ "Occitan (France)",
+ "Oromo",
+ "Oromo (Ethiopia)",
+ "Oromo (Kenya)",
+ "Oriya",
+ "Oriya (India)",
+ "Ossetian (Russia)",
+ "Panjabi (India)",
+ "Papiamento",
+ "Papiamento (Netherlands Antilles)",
+ "Papiamento (Aruba)",
+ "Papiamento (Curaçao)",
+ "Panjabi (Pakistan)",
+ "Polish",
+ "Polish (Poland)",
+ "Pirate",
+ "Pushto (Afghanistan)",
+ "Portuguese",
+ "Portuguese (Brazil)",
+ "Portuguese (Portugal)",
+ "Ayacucho Quechua (Peru)",
+ "Cusco Quechua (Peru)",
+ "Rajasthani (India)",
+ "Romanian",
+ "Romanian (Romania)",
+ "Russian",
+ "Russian (Russia)",
+ "Russian (Ukraine)",
+ "Kinyarwanda (Rwanda)",
+ "Sanskrit (India)",
+ "Santali (India)",
+ "Sardinian (Italy)",
+ "Scots (Scotland)",
+ "Sindhi (India)",
+ "Northern Sami (Norway)",
+ "Samogitian (Lithuania)",
+ "Shuswap (Canada)",
+ "Sidamo (Ethiopia)",
+ "Sinhala",
+ "Sinhala (Sri Lanka)",
+ "Slovak",
+ "Slovak (Slovakia)",
+ "Slovenian",
+ "Slovenian (Slovenia)",
+ "Somali",
+ "Somali (Djibouti)",
+ "Somali (Ethiopia)",
+ "Somali (Kenya)",
+ "Somali (Somalia)",
+ "Songhai languages (Mali)",
+ "Albanian",
+ "Albanian (Albania)",
+ "Albanian (Kosovo)",
+ "Albanian (Macedonia)",
+ "Serbian",
+ "Serbian (Cyrillic)",
+ "Serbian (Latin)",
+ "Serbian (Montenegro)",
+ "Serbian (Serbia)",
+ "Swati (South Africa)",
+ "Southern Sotho (South Africa)",
+ "Swedish",
+ "Swedish (Finland)",
+ "Swedish (Sweden)",
+ "Swahili (Kenya)",
+ "Swahili (Tanzania)",
+ "Silesian (Poland)",
+ "Tamil",
+ "Tamil (India)",
+ "Tamil (Sri Lanka)",
+ "Tulu (India)",
+ "Telugu",
+ "Telugu (India)",
+ "Tajik (Tajikistan)",
+ "Chitwania Tharu (Nepal)",
+ "Thai",
+ "Thai (Thailand)",
+ "Tigrinya",
+ "Tigrinya (Eritrea)",
+ "Tigrinya (Ethiopia)",
+ "Tigre (Eritrea)",
+ "Turkmen (Turkmenistan)",
+ "Tagalog (Philippines)",
+ "Tswana (South Africa)",
+ "Turkish",
+ "Turkish (Cyprus)",
+ "Turkish (Turkey)",
+ "Tsonga (South Africa)",
+ "Tatar",
+ "Tatar (Russia)",
+ "Central Atlas Tamazight",
+ "Central Atlas Tamazight (Marrocos)",
+ "Uighur (China)",
+ "Ukrainian",
+ "Ukrainian (Ukraine)",
+ "Unami (United States)",
+ "Urdu",
+ "Urdu (India)",
+ "Urdu (Pakistan)",
+ "Uzbek",
+ "Uzbek (Uzbekistan)",
+ "Venda (South Africa)",
+ "Vietnamese",
+ "Vietnamese (Vietnam)",
+ "Walloon (Belgium)",
+ "Walser (Switzerland)",
+ "Wolaytta (Ethiopia)",
+ "Wolof (Senegal)",
+ "Xhosa (South Africa)",
+ "Yiddish (United States)",
+ "Yoruba (Nigeria)",
+ "Yue Chinese (Hong Kong)",
+ "Chinese",
+ "Chinese (China)",
+ "Chinese (Hong Kong)",
+ "Chinese (Singapore)",
+ "Chinese (Taiwan)",
+ "Zulu (South Africa)",
+ nullptr
+};
+
+// Windows has some weird locale identifiers which do not honor the ISO 639-1
+// standardized nomenclature. Whenever those don't conflict with existing ISO
+// identifiers, we override them.
+//
+// Reference:
+// - https://msdn.microsoft.com/en-us/library/windows/desktop/ms693062(v=vs.85).aspx
+
+static const char *locale_renames[][2] = {
+ { "in", "id" }, // Indonesian
+ { "iw", "he" }, // Hebrew
+ { "no", "nb" }, // Norwegian Bokmål
+ { nullptr, nullptr }
+};
+
+///////////////////////////////////////////////
+
+Dictionary Translation::_get_messages() const {
+ Dictionary d;
+ for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) {
+ d[E->key()] = E->value();
+ }
+ return d;
+}
+
+Vector<String> Translation::_get_message_list() const {
+ Vector<String> msgs;
+ msgs.resize(translation_map.size());
+ int idx = 0;
+ for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) {
+ msgs.set(idx, E->key());
+ idx += 1;
+ }
+
+ return msgs;
+}
+
+void Translation::_set_messages(const Dictionary &p_messages) {
+ List<Variant> keys;
+ p_messages.get_key_list(&keys);
+ for (const Variant &E : keys) {
+ translation_map[E] = p_messages[E];
+ }
+}
+
+void Translation::set_locale(const String &p_locale) {
+ String univ_locale = TranslationServer::standardize_locale(p_locale);
+
+ if (!TranslationServer::is_locale_valid(univ_locale)) {
+ String trimmed_locale = TranslationServer::get_language_code(univ_locale);
+
+ ERR_FAIL_COND_MSG(!TranslationServer::is_locale_valid(trimmed_locale), "Invalid locale: " + trimmed_locale + ".");
+
+ locale = trimmed_locale;
+ } else {
+ locale = univ_locale;
+ }
+
+ if (OS::get_singleton()->get_main_loop() && TranslationServer::get_singleton()->get_loaded_locales().has(this)) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+}
+
+void Translation::add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context) {
+ translation_map[p_src_text] = p_xlated_text;
+}
+
+void Translation::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) {
+ WARN_PRINT("Translation class doesn't handle plural messages. Calling add_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class");
+ ERR_FAIL_COND_MSG(p_plural_xlated_texts.is_empty(), "Parameter vector p_plural_xlated_texts passed in is empty.");
+ translation_map[p_src_text] = p_plural_xlated_texts[0];
+}
+
+StringName Translation::get_message(const StringName &p_src_text, const StringName &p_context) const {
+ if (p_context != StringName()) {
+ WARN_PRINT("Translation class doesn't handle context. Using context in get_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class");
+ }
+
+ const Map<StringName, StringName>::Element *E = translation_map.find(p_src_text);
+ if (!E) {
+ return StringName();
+ }
+
+ return E->get();
+}
+
+StringName Translation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
+ WARN_PRINT("Translation class doesn't handle plural messages. Calling get_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class");
+ return get_message(p_src_text);
+}
+
+void Translation::erase_message(const StringName &p_src_text, const StringName &p_context) {
+ if (p_context != StringName()) {
+ WARN_PRINT("Translation class doesn't handle context. Using context in erase_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class");
+ }
+
+ translation_map.erase(p_src_text);
+}
+
+void Translation::get_message_list(List<StringName> *r_messages) const {
+ for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) {
+ r_messages->push_back(E->key());
+ }
+}
+
+int Translation::get_message_count() const {
+ return translation_map.size();
+}
+
+void Translation::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_locale", "locale"), &Translation::set_locale);
+ ClassDB::bind_method(D_METHOD("get_locale"), &Translation::get_locale);
+ ClassDB::bind_method(D_METHOD("add_message", "src_message", "xlated_message", "context"), &Translation::add_message, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("add_plural_message", "src_message", "xlated_messages", "context"), &Translation::add_plural_message, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("get_message", "src_message", "context"), &Translation::get_message, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("get_plural_message", "src_message", "src_plural_message", "n", "context"), &Translation::get_plural_message, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("get_message_list"), &Translation::_get_message_list);
+ ClassDB::bind_method(D_METHOD("get_message_count"), &Translation::get_message_count);
+ ClassDB::bind_method(D_METHOD("_set_messages"), &Translation::_set_messages);
+ ClassDB::bind_method(D_METHOD("_get_messages"), &Translation::_get_messages);
+
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "locale"), "set_locale", "get_locale");
+}
+
+///////////////////////////////////////////////
+
+struct _character_accent_pair {
+ const char32_t character;
+ const char32_t *accented_character;
+};
+
+static _character_accent_pair _character_to_accented[] = {
+ { 'A', U"Å" },
+ { 'B', U"ß" },
+ { 'C', U"Ç" },
+ { 'D', U"Ð" },
+ { 'E', U"É" },
+ { 'F', U"F́" },
+ { 'G', U"Ĝ" },
+ { 'H', U"Ĥ" },
+ { 'I', U"Ĩ" },
+ { 'J', U"Ĵ" },
+ { 'K', U"ĸ" },
+ { 'L', U"Ł" },
+ { 'M', U"Ḿ" },
+ { 'N', U"й" },
+ { 'O', U"Ö" },
+ { 'P', U"Ṕ" },
+ { 'Q', U"Q́" },
+ { 'R', U"Ř" },
+ { 'S', U"Ŝ" },
+ { 'T', U"Ŧ" },
+ { 'U', U"Ũ" },
+ { 'V', U"Ṽ" },
+ { 'W', U"Ŵ" },
+ { 'X', U"X́" },
+ { 'Y', U"Ÿ" },
+ { 'Z', U"Ž" },
+ { 'a', U"á" },
+ { 'b', U"ḅ" },
+ { 'c', U"ć" },
+ { 'd', U"d́" },
+ { 'e', U"é" },
+ { 'f', U"f́" },
+ { 'g', U"ǵ" },
+ { 'h', U"h̀" },
+ { 'i', U"í" },
+ { 'j', U"ǰ" },
+ { 'k', U"ḱ" },
+ { 'l', U"ł" },
+ { 'm', U"m̀" },
+ { 'n', U"ή" },
+ { 'o', U"ô" },
+ { 'p', U"ṕ" },
+ { 'q', U"q́" },
+ { 'r', U"ŕ" },
+ { 's', U"š" },
+ { 't', U"ŧ" },
+ { 'u', U"ü" },
+ { 'v', U"ṽ" },
+ { 'w', U"ŵ" },
+ { 'x', U"x́" },
+ { 'y', U"ý" },
+ { 'z', U"ź" },
+};
+
+bool TranslationServer::is_locale_valid(const String &p_locale) {
+ const char **ptr = locale_list;
+
+ while (*ptr) {
+ if (*ptr == p_locale) {
+ return true;
+ }
+ ptr++;
+ }
+
+ return false;
+}
+
+String TranslationServer::standardize_locale(const String &p_locale) {
+ // Replaces '-' with '_' for macOS Sierra-style locales
+ String univ_locale = p_locale.replace("-", "_");
+
+ // Handles known non-ISO locale names used e.g. on Windows
+ int idx = 0;
+ while (locale_renames[idx][0] != nullptr) {
+ if (locale_renames[idx][0] == univ_locale) {
+ univ_locale = locale_renames[idx][1];
+ break;
+ }
+ idx++;
+ }
+
+ return univ_locale;
+}
+
+String TranslationServer::get_language_code(const String &p_locale) {
+ ERR_FAIL_COND_V_MSG(p_locale.length() < 2, p_locale, "Invalid locale '" + p_locale + "'.");
+ // Most language codes are two letters, but some are three,
+ // so we have to look for a regional code separator ('_' or '-')
+ // to extract the left part.
+ // For example we get 'nah_MX' as input and should return 'nah'.
+ int split = p_locale.find("_");
+ if (split == -1) {
+ split = p_locale.find("-");
+ }
+ if (split == -1) { // No separator, so the locale is already only a language code.
+ return p_locale;
+ }
+ return p_locale.left(split);
+}
+
+void TranslationServer::set_locale(const String &p_locale) {
+ String univ_locale = standardize_locale(p_locale);
+
+ if (!is_locale_valid(univ_locale)) {
+ String trimmed_locale = get_language_code(univ_locale);
+ print_verbose(vformat("Unsupported locale '%s', falling back to '%s'.", p_locale, trimmed_locale));
+
+ if (!is_locale_valid(trimmed_locale)) {
+ ERR_PRINT(vformat("Unsupported locale '%s', falling back to 'en'.", trimmed_locale));
+ locale = "en";
+ } else {
+ locale = trimmed_locale;
+ }
+ } else {
+ locale = univ_locale;
+ }
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+
+ ResourceLoader::reload_translation_remaps();
+}
+
+String TranslationServer::get_locale() const {
+ return locale;
+}
+
+String TranslationServer::get_locale_name(const String &p_locale) const {
+ if (!locale_name_map.has(p_locale)) {
+ return String();
+ }
+ return locale_name_map[p_locale];
+}
+
+Array TranslationServer::get_loaded_locales() const {
+ Array locales;
+ for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
+ const Ref<Translation> &t = E->get();
+ ERR_FAIL_COND_V(t.is_null(), Array());
+ String l = t->get_locale();
+
+ locales.push_back(l);
+ }
+
+ return locales;
+}
+
+Vector<String> TranslationServer::get_all_locales() {
+ Vector<String> locales;
+
+ const char **ptr = locale_list;
+
+ while (*ptr) {
+ locales.push_back(*ptr);
+ ptr++;
+ }
+
+ return locales;
+}
+
+Vector<String> TranslationServer::get_all_locale_names() {
+ Vector<String> locales;
+
+ const char **ptr = locale_names;
+
+ while (*ptr) {
+ locales.push_back(String::utf8(*ptr));
+ ptr++;
+ }
+
+ return locales;
+}
+
+void TranslationServer::add_translation(const Ref<Translation> &p_translation) {
+ translations.insert(p_translation);
+}
+
+void TranslationServer::remove_translation(const Ref<Translation> &p_translation) {
+ translations.erase(p_translation);
+}
+
+Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) {
+ Ref<Translation> res;
+ String lang = get_language_code(p_locale);
+ bool near_match_found = false;
+
+ for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
+ const Ref<Translation> &t = E->get();
+ ERR_FAIL_COND_V(t.is_null(), nullptr);
+ String l = t->get_locale();
+
+ // Exact match.
+ if (l == p_locale) {
+ return t;
+ }
+
+ // If near match found, keep that match, but keep looking to try to look for perfect match.
+ if (get_language_code(l) == lang && !near_match_found) {
+ res = t;
+ near_match_found = true;
+ }
+ }
+ return res;
+}
+
+void TranslationServer::clear() {
+ translations.clear();
+}
+
+StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const {
+ // Match given message against the translation catalog for the project locale.
+
+ if (!enabled) {
+ return p_message;
+ }
+
+ ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid.");
+
+ StringName res = _get_message_from_translations(p_message, p_context, locale, false);
+
+ if (!res && fallback.length() >= 2) {
+ res = _get_message_from_translations(p_message, p_context, fallback, false);
+ }
+
+ if (!res) {
+ return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message;
+ }
+
+ return pseudolocalization_enabled ? pseudolocalize(res) : res;
+}
+
+StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (!enabled) {
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+ }
+
+ ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid.");
+
+ StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n);
+
+ if (!res && fallback.length() >= 2) {
+ res = _get_message_from_translations(p_message, p_context, fallback, true, p_message_plural, p_n);
+ }
+
+ if (!res) {
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+ }
+
+ return res;
+}
+
+StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const {
+ // Locale can be of the form 'll_CC', i.e. language code and regional code,
+ // e.g. 'en_US', 'en_GB', etc. It might also be simply 'll', e.g. 'en'.
+ // To find the relevant translation, we look for those with locale starting
+ // with the language code, and then if any is an exact match for the long
+ // form. If not found, we fall back to a near match (another locale with
+ // same language code).
+
+ // Note: ResourceLoader::_path_remap reproduces this locale near matching
+ // logic, so be sure to propagate changes there when changing things here.
+
+ StringName res;
+ String lang = get_language_code(p_locale);
+ bool near_match = false;
+
+ for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
+ const Ref<Translation> &t = E->get();
+ ERR_FAIL_COND_V(t.is_null(), p_message);
+ String l = t->get_locale();
+
+ bool exact_match = (l == p_locale);
+ if (!exact_match) {
+ if (near_match) {
+ continue; // Only near-match once, but keep looking for exact matches.
+ }
+ if (get_language_code(l) != lang) {
+ continue; // Language code does not match.
+ }
+ }
+
+ StringName r;
+ if (!plural) {
+ r = t->get_message(p_message, p_context);
+ } else {
+ r = t->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ }
+
+ if (!r) {
+ continue;
+ }
+ res = r;
+
+ if (exact_match) {
+ break;
+ } else {
+ near_match = true;
+ }
+ }
+
+ return res;
+}
+
+TranslationServer *TranslationServer::singleton = nullptr;
+
+bool TranslationServer::_load_translations(const String &p_from) {
+ if (ProjectSettings::get_singleton()->has_setting(p_from)) {
+ Vector<String> translations = ProjectSettings::get_singleton()->get(p_from);
+
+ int tcount = translations.size();
+
+ if (tcount) {
+ const String *r = translations.ptr();
+
+ for (int i = 0; i < tcount; i++) {
+ Ref<Translation> tr = ResourceLoader::load(r[i]);
+ if (tr.is_valid()) {
+ add_translation(tr);
+ }
+ }
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void TranslationServer::setup() {
+ String test = GLOBAL_DEF("internationalization/locale/test", "");
+ test = test.strip_edges();
+ if (test != "") {
+ set_locale(test);
+ } else {
+ set_locale(OS::get_singleton()->get_locale());
+ }
+
+ fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
+ pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false);
+ pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true);
+ pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false);
+ pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false);
+ pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false);
+ expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0);
+ pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "[");
+ pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]");
+ pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true);
+
+#ifdef TOOLS_ENABLED
+ {
+ String options = "";
+ int idx = 0;
+ while (locale_list[idx]) {
+ if (idx > 0) {
+ options += ",";
+ }
+ options += locale_list[idx];
+ idx++;
+ }
+ ProjectSettings::get_singleton()->set_custom_property_info("internationalization/locale/fallback", PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_ENUM, options));
+ }
+#endif
+}
+
+void TranslationServer::set_tool_translation(const Ref<Translation> &p_translation) {
+ tool_translation = p_translation;
+}
+
+Ref<Translation> TranslationServer::get_tool_translation() const {
+ return tool_translation;
+}
+
+String TranslationServer::get_tool_locale() {
+#ifdef TOOLS_ENABLED
+ if (TranslationServer::get_singleton()->get_tool_translation().is_valid() && (Engine::get_singleton()->is_editor_hint() || Main::is_project_manager())) {
+ return tool_translation->get_locale();
+ } else {
+#else
+ {
+#endif
+ return get_locale();
+ }
+}
+
+StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const {
+ if (tool_translation.is_valid()) {
+ StringName r = tool_translation->get_message(p_message, p_context);
+ if (r) {
+ return editor_pseudolocalization ? tool_pseudolocalize(r) : r;
+ }
+ }
+ return editor_pseudolocalization ? tool_pseudolocalize(p_message) : p_message;
+}
+
+StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (tool_translation.is_valid()) {
+ StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
+void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) {
+ doc_translation = p_translation;
+}
+
+StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const {
+ if (doc_translation.is_valid()) {
+ StringName r = doc_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (doc_translation.is_valid()) {
+ StringName r = doc_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
+bool TranslationServer::is_pseudolocalization_enabled() const {
+ return pseudolocalization_enabled;
+}
+
+void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
+ pseudolocalization_enabled = p_enabled;
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+ ResourceLoader::reload_translation_remaps();
+}
+
+void TranslationServer::set_editor_pseudolocalization(bool p_enabled) {
+ editor_pseudolocalization = p_enabled;
+}
+
+void TranslationServer::reload_pseudolocalization() {
+ pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents");
+ pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels");
+ pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi");
+ pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override");
+ expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio");
+ pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix");
+ pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix");
+ pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders");
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+ ResourceLoader::reload_translation_remaps();
+}
+
+StringName TranslationServer::pseudolocalize(const StringName &p_message) const {
+ String message = p_message;
+ int length = message.length();
+ if (pseudolocalization_override_enabled) {
+ message = get_override_string(message);
+ }
+
+ if (pseudolocalization_double_vowels_enabled) {
+ message = double_vowels(message);
+ }
+
+ if (pseudolocalization_accents_enabled) {
+ message = replace_with_accented_string(message);
+ }
+
+ if (pseudolocalization_fake_bidi_enabled) {
+ message = wrap_with_fakebidi_characters(message);
+ }
+
+ StringName res = add_padding(message, length);
+ return res;
+}
+
+StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const {
+ String message = p_message;
+ message = double_vowels(message);
+ message = replace_with_accented_string(message);
+ StringName res = "[!!! " + message + " !!!]";
+ return res;
+}
+
+String TranslationServer::get_override_string(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.size(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += '*';
+ }
+ return res;
+}
+
+String TranslationServer::double_vowels(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.size(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += p_message[i];
+ if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
+ p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
+ res += p_message[i];
+ }
+ }
+ return res;
+};
+
+String TranslationServer::replace_with_accented_string(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.size(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ const char32_t *accented = get_accented_version(p_message[i]);
+ if (accented) {
+ res += accented;
+ } else {
+ res += p_message[i];
+ }
+ }
+ return res;
+}
+
+String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const {
+ String res;
+ char32_t fakebidiprefix = U'\u202e';
+ char32_t fakebidisuffix = U'\u202c';
+ res += fakebidiprefix;
+ // The fake bidi unicode gets popped at every newline so pushing it back at every newline.
+ for (int i = 0; i < p_message.size(); i++) {
+ if (p_message[i] == '\n') {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += fakebidiprefix;
+ } else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += p_message[i + 1];
+ res += fakebidiprefix;
+ i++;
+ } else {
+ res += p_message[i];
+ }
+ }
+ res += fakebidisuffix;
+ return res;
+}
+
+String TranslationServer::add_padding(String &p_message, int p_length) const {
+ String res;
+ String prefix = pseudolocalization_prefix;
+ String suffix;
+ for (int i = 0; i < p_length * expansion_ratio / 2; i++) {
+ prefix += "_";
+ suffix += "_";
+ }
+ suffix += pseudolocalization_suffix;
+ res += prefix;
+ res += p_message;
+ res += suffix;
+ return res;
+}
+
+const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
+ if (!((p_character >= 'a' && p_character <= 'z') || (p_character >= 'A' && p_character <= 'Z'))) {
+ return nullptr;
+ }
+
+ for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
+ if (_character_to_accented[i].character == p_character) {
+ return _character_to_accented[i].accented_character;
+ }
+ }
+
+ return nullptr;
+}
+
+bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
+ return p_message[p_index] == '%' && p_index < p_message.size() - 1 &&
+ (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
+ p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
+}
+
+void TranslationServer::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
+ ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
+
+ ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name);
+
+ ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(""));
+
+ ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation);
+ ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation);
+ ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object);
+
+ ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
+
+ ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
+
+ ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization);
+ ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize);
+ ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
+}
+
+void TranslationServer::load_translations() {
+ String locale = get_locale();
+ _load_translations("internationalization/locale/translations"); //all
+ _load_translations("internationalization/locale/translations_" + locale.substr(0, 2));
+
+ if (locale.substr(0, 2) != locale) {
+ _load_translations("internationalization/locale/translations_" + locale);
+ }
+}
+
+TranslationServer::TranslationServer() {
+ singleton = this;
+
+ for (int i = 0; locale_list[i]; ++i) {
+ locale_name_map.insert(locale_list[i], String::utf8(locale_names[i]));
+ }
+}
diff --git a/core/string/translation.h b/core/string/translation.h
new file mode 100644
index 0000000000..4f179ac0fe
--- /dev/null
+++ b/core/string/translation.h
@@ -0,0 +1,158 @@
+/*************************************************************************/
+/* translation.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TRANSLATION_H
+#define TRANSLATION_H
+
+#include "core/io/resource.h"
+
+class Translation : public Resource {
+ GDCLASS(Translation, Resource);
+ OBJ_SAVE_TYPE(Translation);
+ RES_BASE_EXTENSION("translation");
+
+ String locale = "en";
+ Map<StringName, StringName> translation_map;
+
+ virtual Vector<String> _get_message_list() const;
+ virtual Dictionary _get_messages() const;
+ virtual void _set_messages(const Dictionary &p_messages);
+
+protected:
+ static void _bind_methods();
+
+public:
+ void set_locale(const String &p_locale);
+ _FORCE_INLINE_ String get_locale() const { return locale; }
+
+ virtual void add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context = "");
+ virtual void add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context = "");
+ virtual StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const; //overridable for other implementations
+ virtual StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const;
+ virtual void erase_message(const StringName &p_src_text, const StringName &p_context = "");
+ virtual void get_message_list(List<StringName> *r_messages) const;
+ virtual int get_message_count() const;
+
+ Translation() {}
+};
+
+class TranslationServer : public Object {
+ GDCLASS(TranslationServer, Object);
+
+ String locale = "en";
+ String fallback;
+
+ Set<Ref<Translation>> translations;
+ Ref<Translation> tool_translation;
+ Ref<Translation> doc_translation;
+
+ Map<String, String> locale_name_map;
+
+ bool enabled = true;
+
+ bool pseudolocalization_enabled = false;
+ bool pseudolocalization_accents_enabled = false;
+ bool pseudolocalization_double_vowels_enabled = false;
+ bool pseudolocalization_fake_bidi_enabled = false;
+ bool pseudolocalization_override_enabled = false;
+ bool pseudolocalization_skip_placeholders_enabled = false;
+ bool editor_pseudolocalization = false;
+ float expansion_ratio = 0.0;
+ String pseudolocalization_prefix;
+ String pseudolocalization_suffix;
+
+ StringName tool_pseudolocalize(const StringName &p_message) const;
+ String get_override_string(String &p_message) const;
+ String double_vowels(String &p_message) const;
+ String replace_with_accented_string(String &p_message) const;
+ String wrap_with_fakebidi_characters(String &p_message) const;
+ String add_padding(String &p_message, int p_length) const;
+ const char32_t *get_accented_version(char32_t p_character) const;
+ bool is_placeholder(String &p_message, int p_index) const;
+
+ static TranslationServer *singleton;
+ bool _load_translations(const String &p_from);
+
+ StringName _get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural = "", int p_n = 0) const;
+
+ static void _bind_methods();
+
+public:
+ _FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; }
+
+ void set_enabled(bool p_enabled) { enabled = p_enabled; }
+ _FORCE_INLINE_ bool is_enabled() const { return enabled; }
+
+ void set_locale(const String &p_locale);
+ String get_locale() const;
+ Ref<Translation> get_translation_object(const String &p_locale);
+
+ String get_locale_name(const String &p_locale) const;
+
+ Array get_loaded_locales() const;
+
+ void add_translation(const Ref<Translation> &p_translation);
+ void remove_translation(const Ref<Translation> &p_translation);
+
+ StringName translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+
+ StringName pseudolocalize(const StringName &p_message) const;
+
+ bool is_pseudolocalization_enabled() const;
+ void set_pseudolocalization_enabled(bool p_enabled);
+ void set_editor_pseudolocalization(bool p_enabled);
+ void reload_pseudolocalization();
+
+ static Vector<String> get_all_locales();
+ static Vector<String> get_all_locale_names();
+ static bool is_locale_valid(const String &p_locale);
+ static String standardize_locale(const String &p_locale);
+ static String get_language_code(const String &p_locale);
+
+ String get_tool_locale();
+ void set_tool_translation(const Ref<Translation> &p_translation);
+ Ref<Translation> get_tool_translation() const;
+ StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+ void set_doc_translation(const Ref<Translation> &p_translation);
+ StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+
+ void setup();
+
+ void clear();
+
+ void load_translations();
+
+ TranslationServer();
+};
+
+#endif // TRANSLATION_H
diff --git a/core/string/translation_po.cpp b/core/string/translation_po.cpp
new file mode 100644
index 0000000000..1da00aa54b
--- /dev/null
+++ b/core/string/translation_po.cpp
@@ -0,0 +1,309 @@
+/*************************************************************************/
+/* translation_po.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "translation_po.h"
+
+#include "core/io/file_access.h"
+
+#ifdef DEBUG_TRANSLATION_PO
+void TranslationPO::print_translation_map() {
+ Error err;
+ FileAccess *file = FileAccess::open("translation_map_print_test.txt", FileAccess::WRITE, &err);
+ if (err != OK) {
+ ERR_PRINT("Failed to open translation_map_print_test.txt");
+ return;
+ }
+
+ file->store_line("NPlural : " + String::num_int64(this->get_plural_forms()));
+ file->store_line("Plural rule : " + this->get_plural_rule());
+ file->store_line("");
+
+ List<StringName> context_l;
+ translation_map.get_key_list(&context_l);
+ for (const StringName &ctx : context_l) {
+ file->store_line(" ===== Context: " + String::utf8(String(ctx).utf8()) + " ===== ");
+ const HashMap<StringName, Vector<StringName>> &inner_map = translation_map[ctx];
+
+ List<StringName> id_l;
+ inner_map.get_key_list(&id_l);
+ for (List<StringName>::Element *E2 = id_l.front(); E2; E2 = E2->next()) {
+ StringName id = E2->get();
+ file->store_line("msgid: " + String::utf8(String(id).utf8()));
+ for (int i = 0; i < inner_map[id].size(); i++) {
+ file->store_line("msgstr[" + String::num_int64(i) + "]: " + String::utf8(String(inner_map[id][i]).utf8()));
+ }
+ file->store_line("");
+ }
+ }
+ file->close();
+}
+#endif
+
+Dictionary TranslationPO::_get_messages() const {
+ // Return translation_map as a Dictionary.
+
+ Dictionary d;
+
+ List<StringName> context_l;
+ translation_map.get_key_list(&context_l);
+ for (const StringName &ctx : context_l) {
+ const HashMap<StringName, Vector<StringName>> &id_str_map = translation_map[ctx];
+
+ Dictionary d2;
+ List<StringName> id_l;
+ id_str_map.get_key_list(&id_l);
+ // Save list of id and strs associated with a context in a temporary dictionary.
+ for (List<StringName>::Element *E2 = id_l.front(); E2; E2 = E2->next()) {
+ StringName id = E2->get();
+ d2[id] = id_str_map[id];
+ }
+
+ d[ctx] = d2;
+ }
+
+ return d;
+}
+
+void TranslationPO::_set_messages(const Dictionary &p_messages) {
+ // Construct translation_map from a Dictionary.
+
+ List<Variant> context_l;
+ p_messages.get_key_list(&context_l);
+ for (const Variant &ctx : context_l) {
+ const Dictionary &id_str_map = p_messages[ctx];
+
+ HashMap<StringName, Vector<StringName>> temp_map;
+ List<Variant> id_l;
+ id_str_map.get_key_list(&id_l);
+ for (List<Variant>::Element *E2 = id_l.front(); E2; E2 = E2->next()) {
+ StringName id = E2->get();
+ temp_map[id] = id_str_map[id];
+ }
+
+ translation_map[ctx] = temp_map;
+ }
+}
+
+Vector<String> TranslationPO::_get_message_list() const {
+ // Return all keys in translation_map.
+
+ List<StringName> msgs;
+ get_message_list(&msgs);
+
+ Vector<String> v;
+ for (const StringName &E : msgs) {
+ v.push_back(E);
+ }
+
+ return v;
+}
+
+int TranslationPO::_get_plural_index(int p_n) const {
+ // Get a number between [0;number of plural forms).
+
+ input_val.clear();
+ input_val.push_back(p_n);
+
+ Variant result;
+ for (int i = 0; i < equi_tests.size(); i++) {
+ Error err = expr->parse(equi_tests[i], input_name);
+ ERR_FAIL_COND_V_MSG(err != OK, 0, "Cannot parse expression. Error: " + expr->get_error_text());
+
+ result = expr->execute(input_val);
+ ERR_FAIL_COND_V_MSG(expr->has_execute_failed(), 0, "Cannot evaluate expression.");
+
+ // Last expression. Variant result will either map to a bool or an integer, in both cases returning it will give the correct plural index.
+ if (i + 1 == equi_tests.size()) {
+ return result;
+ }
+
+ if (bool(result)) {
+ return i;
+ }
+ }
+
+ ERR_FAIL_V_MSG(0, "Unexpected. Function should have returned. Please report this bug.");
+}
+
+void TranslationPO::_cache_plural_tests(const String &p_plural_rule) {
+ // Some examples of p_plural_rule passed in can have the form:
+ // "n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5" (Arabic)
+ // "n >= 2" (French) // When evaluating the last, especially careful with this one.
+ // "n != 1" (English)
+ int first_ques_mark = p_plural_rule.find("?");
+ if (first_ques_mark == -1) {
+ equi_tests.push_back(p_plural_rule.strip_edges());
+ return;
+ }
+
+ String equi_test = p_plural_rule.substr(0, first_ques_mark).strip_edges();
+ equi_tests.push_back(equi_test);
+
+ String after_colon = p_plural_rule.substr(p_plural_rule.find(":") + 1, p_plural_rule.length());
+ _cache_plural_tests(after_colon);
+}
+
+void TranslationPO::set_plural_rule(const String &p_plural_rule) {
+ // Set plural_forms and plural_rule.
+ // p_plural_rule passed in has the form "Plural-Forms: nplurals=2; plural=(n >= 2);".
+
+ int first_semi_col = p_plural_rule.find(";");
+ plural_forms = p_plural_rule.substr(p_plural_rule.find("=") + 1, first_semi_col - (p_plural_rule.find("=") + 1)).to_int();
+
+ int expression_start = p_plural_rule.find("=", first_semi_col) + 1;
+ int second_semi_col = p_plural_rule.rfind(";");
+ plural_rule = p_plural_rule.substr(expression_start, second_semi_col - expression_start);
+
+ // Setup the cache to make evaluating plural rule faster later on.
+ plural_rule = plural_rule.replacen("(", "");
+ plural_rule = plural_rule.replacen(")", "");
+ _cache_plural_tests(plural_rule);
+ expr.instantiate();
+ input_name.push_back("n");
+}
+
+void TranslationPO::add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context) {
+ HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
+
+ if (map_id_str.has(p_src_text)) {
+ WARN_PRINT("Double translations for \"" + String(p_src_text) + "\" under the same context \"" + String(p_context) + "\" for locale \"" + get_locale() + "\".\nThere should only be one unique translation for a given string under the same context.");
+ map_id_str[p_src_text].set(0, p_xlated_text);
+ } else {
+ map_id_str[p_src_text].push_back(p_xlated_text);
+ }
+}
+
+void TranslationPO::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) {
+ ERR_FAIL_COND_MSG(p_plural_xlated_texts.size() != plural_forms, "Trying to add plural texts that don't match the required number of plural forms for locale \"" + get_locale() + "\"");
+
+ HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
+
+ if (map_id_str.has(p_src_text)) {
+ WARN_PRINT("Double translations for \"" + p_src_text + "\" under the same context \"" + p_context + "\" for locale " + get_locale() + ".\nThere should only be one unique translation for a given string under the same context.");
+ map_id_str[p_src_text].clear();
+ }
+
+ for (int i = 0; i < p_plural_xlated_texts.size(); i++) {
+ map_id_str[p_src_text].push_back(p_plural_xlated_texts[i]);
+ }
+}
+
+int TranslationPO::get_plural_forms() const {
+ return plural_forms;
+}
+
+String TranslationPO::get_plural_rule() const {
+ return plural_rule;
+}
+
+StringName TranslationPO::get_message(const StringName &p_src_text, const StringName &p_context) const {
+ if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
+ return StringName();
+ }
+ ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug.");
+
+ return translation_map[p_context][p_src_text][0];
+}
+
+StringName TranslationPO::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
+ ERR_FAIL_COND_V_MSG(p_n < 0, StringName(), "N passed into translation to get a plural message should not be negative. For negative numbers, use singular translation please. Search \"gettext PO Plural Forms\" online for the documentation on translating negative numbers.");
+
+ // If the query is the same as last time, return the cached result.
+ if (p_n == last_plural_n && p_context == last_plural_context && p_src_text == last_plural_key) {
+ return translation_map[p_context][p_src_text][last_plural_mapped_index];
+ }
+
+ if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
+ return StringName();
+ }
+ ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug.");
+
+ if (translation_map[p_context][p_src_text].size() == 1) {
+ WARN_PRINT("Source string \"" + String(p_src_text) + "\" doesn't have plural translations. Use singular translation API for such as tr(), TTR() to translate \"" + String(p_src_text) + "\"");
+ return translation_map[p_context][p_src_text][0];
+ }
+
+ int plural_index = _get_plural_index(p_n);
+ ERR_FAIL_COND_V_MSG(plural_index < 0 || translation_map[p_context][p_src_text].size() < plural_index + 1, StringName(), "Plural index returned or number of plural translations is not valid. Please report this bug.");
+
+ // Cache result so that if the next entry is the same, we can return directly.
+ // _get_plural_index(p_n) can get very costly, especially when evaluating long plural-rule (Arabic)
+ last_plural_key = p_src_text;
+ last_plural_context = p_context;
+ last_plural_n = p_n;
+ last_plural_mapped_index = plural_index;
+
+ return translation_map[p_context][p_src_text][plural_index];
+}
+
+void TranslationPO::erase_message(const StringName &p_src_text, const StringName &p_context) {
+ if (!translation_map.has(p_context)) {
+ return;
+ }
+
+ translation_map[p_context].erase(p_src_text);
+}
+
+void TranslationPO::get_message_list(List<StringName> *r_messages) const {
+ // OptimizedTranslation uses this function to get the list of msgid.
+ // Return all the keys of translation_map under "" context.
+
+ List<StringName> context_l;
+ translation_map.get_key_list(&context_l);
+
+ for (const StringName &E : context_l) {
+ if (String(E) != "") {
+ continue;
+ }
+
+ List<StringName> msgid_l;
+ translation_map[E].get_key_list(&msgid_l);
+
+ for (List<StringName>::Element *E2 = msgid_l.front(); E2; E2 = E2->next()) {
+ r_messages->push_back(E2->get());
+ }
+ }
+}
+
+int TranslationPO::get_message_count() const {
+ List<StringName> context_l;
+ translation_map.get_key_list(&context_l);
+
+ int count = 0;
+ for (const StringName &E : context_l) {
+ count += translation_map[E].size();
+ }
+ return count;
+}
+
+void TranslationPO::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_plural_forms"), &TranslationPO::get_plural_forms);
+ ClassDB::bind_method(D_METHOD("get_plural_rule"), &TranslationPO::get_plural_rule);
+}
diff --git a/core/string/translation_po.h b/core/string/translation_po.h
new file mode 100644
index 0000000000..0e1d03d6ca
--- /dev/null
+++ b/core/string/translation_po.h
@@ -0,0 +1,92 @@
+/*************************************************************************/
+/* translation_po.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TRANSLATION_PO_H
+#define TRANSLATION_PO_H
+
+//#define DEBUG_TRANSLATION_PO
+
+#include "core/math/expression.h"
+#include "core/string/translation.h"
+
+class TranslationPO : public Translation {
+ GDCLASS(TranslationPO, Translation);
+
+ // TLDR: Maps context to a list of source strings and translated strings. In PO terms, maps msgctxt to a list of msgid and msgstr.
+ // The first key corresponds to context, and the second key (of the contained HashMap) corresponds to source string.
+ // The value Vector<StringName> in the second map stores the translated strings. Index 0, 1, 2 matches msgstr[0], msgstr[1], msgstr[2]... in the case of plurals.
+ // Otherwise index 0 matches to msgstr in a singular translation.
+ // Strings without context have "" as first key.
+ HashMap<StringName, HashMap<StringName, Vector<StringName>>> translation_map;
+
+ int plural_forms = 0; // 0 means no "Plural-Forms" is given in the PO header file. The min for all languages is 1.
+ String plural_rule;
+
+ // Cache temporary variables related to _get_plural_index() to make it faster
+ Vector<String> equi_tests;
+ Vector<String> input_name;
+ mutable Ref<Expression> expr;
+ mutable Array input_val;
+ mutable StringName last_plural_key;
+ mutable StringName last_plural_context;
+ mutable int last_plural_n = -1; // Set it to an impossible value at the beginning.
+ mutable int last_plural_mapped_index = 0;
+
+ void _cache_plural_tests(const String &p_plural_rule);
+ int _get_plural_index(int p_n) const;
+
+ Vector<String> _get_message_list() const override;
+ Dictionary _get_messages() const override;
+ void _set_messages(const Dictionary &p_messages) override;
+
+protected:
+ static void _bind_methods();
+
+public:
+ void get_message_list(List<StringName> *r_messages) const override;
+ int get_message_count() const override;
+ void add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context = "") override;
+ void add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context = "") override;
+ StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const override;
+ StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const override;
+ void erase_message(const StringName &p_src_text, const StringName &p_context = "") override;
+
+ void set_plural_rule(const String &p_plural_rule);
+ int get_plural_forms() const;
+ String get_plural_rule() const;
+
+#ifdef DEBUG_TRANSLATION_PO
+ void print_translation_map();
+#endif
+
+ TranslationPO() {}
+};
+
+#endif // TRANSLATION_PO_H
diff --git a/core/string/ucaps.h b/core/string/ucaps.h
new file mode 100644
index 0000000000..b785ac7879
--- /dev/null
+++ b/core/string/ucaps.h
@@ -0,0 +1,1415 @@
+/*************************************************************************/
+/* ucaps.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef UCAPS_H
+#define UCAPS_H
+
+//satan invented unicode?
+#define CAPS_LEN 666
+
+static const int caps_table[CAPS_LEN][2] = {
+ { 0x0061, 0x0041 },
+ { 0x0062, 0x0042 },
+ { 0x0063, 0x0043 },
+ { 0x0064, 0x0044 },
+ { 0x0065, 0x0045 },
+ { 0x0066, 0x0046 },
+ { 0x0067, 0x0047 },
+ { 0x0068, 0x0048 },
+ { 0x0069, 0x0049 },
+ { 0x006A, 0x004A },
+ { 0x006B, 0x004B },
+ { 0x006C, 0x004C },
+ { 0x006D, 0x004D },
+ { 0x006E, 0x004E },
+ { 0x006F, 0x004F },
+ { 0x0070, 0x0050 },
+ { 0x0071, 0x0051 },
+ { 0x0072, 0x0052 },
+ { 0x0073, 0x0053 },
+ { 0x0074, 0x0054 },
+ { 0x0075, 0x0055 },
+ { 0x0076, 0x0056 },
+ { 0x0077, 0x0057 },
+ { 0x0078, 0x0058 },
+ { 0x0079, 0x0059 },
+ { 0x007A, 0x005A },
+ { 0x00E0, 0x00C0 },
+ { 0x00E1, 0x00C1 },
+ { 0x00E2, 0x00C2 },
+ { 0x00E3, 0x00C3 },
+ { 0x00E4, 0x00C4 },
+ { 0x00E5, 0x00C5 },
+ { 0x00E6, 0x00C6 },
+ { 0x00E7, 0x00C7 },
+ { 0x00E8, 0x00C8 },
+ { 0x00E9, 0x00C9 },
+ { 0x00EA, 0x00CA },
+ { 0x00EB, 0x00CB },
+ { 0x00EC, 0x00CC },
+ { 0x00ED, 0x00CD },
+ { 0x00EE, 0x00CE },
+ { 0x00EF, 0x00CF },
+ { 0x00F0, 0x00D0 },
+ { 0x00F1, 0x00D1 },
+ { 0x00F2, 0x00D2 },
+ { 0x00F3, 0x00D3 },
+ { 0x00F4, 0x00D4 },
+ { 0x00F5, 0x00D5 },
+ { 0x00F6, 0x00D6 },
+ { 0x00F8, 0x00D8 },
+ { 0x00F9, 0x00D9 },
+ { 0x00FA, 0x00DA },
+ { 0x00FB, 0x00DB },
+ { 0x00FC, 0x00DC },
+ { 0x00FD, 0x00DD },
+ { 0x00FE, 0x00DE },
+ { 0x00FF, 0x0178 },
+ { 0x0101, 0x0100 },
+ { 0x0103, 0x0102 },
+ { 0x0105, 0x0104 },
+ { 0x0107, 0x0106 },
+ { 0x0109, 0x0108 },
+ { 0x010B, 0x010A },
+ { 0x010D, 0x010C },
+ { 0x010F, 0x010E },
+ { 0x0111, 0x0110 },
+ { 0x0113, 0x0112 },
+ { 0x0115, 0x0114 },
+ { 0x0117, 0x0116 },
+ { 0x0119, 0x0118 },
+ { 0x011B, 0x011A },
+ { 0x011D, 0x011C },
+ { 0x011F, 0x011E },
+ { 0x0121, 0x0120 },
+ { 0x0123, 0x0122 },
+ { 0x0125, 0x0124 },
+ { 0x0127, 0x0126 },
+ { 0x0129, 0x0128 },
+ { 0x012B, 0x012A },
+ { 0x012D, 0x012C },
+ { 0x012F, 0x012E },
+ { 0x0131, 0x0049 },
+ { 0x0133, 0x0132 },
+ { 0x0135, 0x0134 },
+ { 0x0137, 0x0136 },
+ { 0x013A, 0x0139 },
+ { 0x013C, 0x013B },
+ { 0x013E, 0x013D },
+ { 0x0140, 0x013F },
+ { 0x0142, 0x0141 },
+ { 0x0144, 0x0143 },
+ { 0x0146, 0x0145 },
+ { 0x0148, 0x0147 },
+ { 0x014B, 0x014A },
+ { 0x014D, 0x014C },
+ { 0x014F, 0x014E },
+ { 0x0151, 0x0150 },
+ { 0x0153, 0x0152 },
+ { 0x0155, 0x0154 },
+ { 0x0157, 0x0156 },
+ { 0x0159, 0x0158 },
+ { 0x015B, 0x015A },
+ { 0x015D, 0x015C },
+ { 0x015F, 0x015E },
+ { 0x0161, 0x0160 },
+ { 0x0163, 0x0162 },
+ { 0x0165, 0x0164 },
+ { 0x0167, 0x0166 },
+ { 0x0169, 0x0168 },
+ { 0x016B, 0x016A },
+ { 0x016D, 0x016C },
+ { 0x016F, 0x016E },
+ { 0x0171, 0x0170 },
+ { 0x0173, 0x0172 },
+ { 0x0175, 0x0174 },
+ { 0x0177, 0x0176 },
+ { 0x017A, 0x0179 },
+ { 0x017C, 0x017B },
+ { 0x017E, 0x017D },
+ { 0x0183, 0x0182 },
+ { 0x0185, 0x0184 },
+ { 0x0188, 0x0187 },
+ { 0x018C, 0x018B },
+ { 0x0192, 0x0191 },
+ { 0x0199, 0x0198 },
+ { 0x01A1, 0x01A0 },
+ { 0x01A3, 0x01A2 },
+ { 0x01A5, 0x01A4 },
+ { 0x01A8, 0x01A7 },
+ { 0x01AD, 0x01AC },
+ { 0x01B0, 0x01AF },
+ { 0x01B4, 0x01B3 },
+ { 0x01B6, 0x01B5 },
+ { 0x01B9, 0x01B8 },
+ { 0x01BD, 0x01BC },
+ { 0x01C6, 0x01C4 },
+ { 0x01C9, 0x01C7 },
+ { 0x01CC, 0x01CA },
+ { 0x01CE, 0x01CD },
+ { 0x01D0, 0x01CF },
+ { 0x01D2, 0x01D1 },
+ { 0x01D4, 0x01D3 },
+ { 0x01D6, 0x01D5 },
+ { 0x01D8, 0x01D7 },
+ { 0x01DA, 0x01D9 },
+ { 0x01DC, 0x01DB },
+ { 0x01DF, 0x01DE },
+ { 0x01E1, 0x01E0 },
+ { 0x01E3, 0x01E2 },
+ { 0x01E5, 0x01E4 },
+ { 0x01E7, 0x01E6 },
+ { 0x01E9, 0x01E8 },
+ { 0x01EB, 0x01EA },
+ { 0x01ED, 0x01EC },
+ { 0x01EF, 0x01EE },
+ { 0x01F3, 0x01F1 },
+ { 0x01F5, 0x01F4 },
+ { 0x01FB, 0x01FA },
+ { 0x01FD, 0x01FC },
+ { 0x01FF, 0x01FE },
+ { 0x0201, 0x0200 },
+ { 0x0203, 0x0202 },
+ { 0x0205, 0x0204 },
+ { 0x0207, 0x0206 },
+ { 0x0209, 0x0208 },
+ { 0x020B, 0x020A },
+ { 0x020D, 0x020C },
+ { 0x020F, 0x020E },
+ { 0x0211, 0x0210 },
+ { 0x0213, 0x0212 },
+ { 0x0215, 0x0214 },
+ { 0x0217, 0x0216 },
+ { 0x0253, 0x0181 },
+ { 0x0254, 0x0186 },
+ { 0x0257, 0x018A },
+ { 0x0258, 0x018E },
+ { 0x0259, 0x018F },
+ { 0x025B, 0x0190 },
+ { 0x0260, 0x0193 },
+ { 0x0263, 0x0194 },
+ { 0x0268, 0x0197 },
+ { 0x0269, 0x0196 },
+ { 0x026F, 0x019C },
+ { 0x0272, 0x019D },
+ { 0x0275, 0x019F },
+ { 0x0283, 0x01A9 },
+ { 0x0288, 0x01AE },
+ { 0x028A, 0x01B1 },
+ { 0x028B, 0x01B2 },
+ { 0x0292, 0x01B7 },
+ { 0x03AC, 0x0386 },
+ { 0x03AD, 0x0388 },
+ { 0x03AE, 0x0389 },
+ { 0x03AF, 0x038A },
+ { 0x03B1, 0x0391 },
+ { 0x03B2, 0x0392 },
+ { 0x03B3, 0x0393 },
+ { 0x03B4, 0x0394 },
+ { 0x03B5, 0x0395 },
+ { 0x03B6, 0x0396 },
+ { 0x03B7, 0x0397 },
+ { 0x03B8, 0x0398 },
+ { 0x03B9, 0x0399 },
+ { 0x03BA, 0x039A },
+ { 0x03BB, 0x039B },
+ { 0x03BC, 0x039C },
+ { 0x03BD, 0x039D },
+ { 0x03BE, 0x039E },
+ { 0x03BF, 0x039F },
+ { 0x03C0, 0x03A0 },
+ { 0x03C1, 0x03A1 },
+ { 0x03C3, 0x03A3 },
+ { 0x03C4, 0x03A4 },
+ { 0x03C5, 0x03A5 },
+ { 0x03C6, 0x03A6 },
+ { 0x03C7, 0x03A7 },
+ { 0x03C8, 0x03A8 },
+ { 0x03C9, 0x03A9 },
+ { 0x03CA, 0x03AA },
+ { 0x03CB, 0x03AB },
+ { 0x03CC, 0x038C },
+ { 0x03CD, 0x038E },
+ { 0x03CE, 0x038F },
+ { 0x03E3, 0x03E2 },
+ { 0x03E5, 0x03E4 },
+ { 0x03E7, 0x03E6 },
+ { 0x03E9, 0x03E8 },
+ { 0x03EB, 0x03EA },
+ { 0x03ED, 0x03EC },
+ { 0x03EF, 0x03EE },
+ { 0x0430, 0x0410 },
+ { 0x0431, 0x0411 },
+ { 0x0432, 0x0412 },
+ { 0x0433, 0x0413 },
+ { 0x0434, 0x0414 },
+ { 0x0435, 0x0415 },
+ { 0x0436, 0x0416 },
+ { 0x0437, 0x0417 },
+ { 0x0438, 0x0418 },
+ { 0x0439, 0x0419 },
+ { 0x043A, 0x041A },
+ { 0x043B, 0x041B },
+ { 0x043C, 0x041C },
+ { 0x043D, 0x041D },
+ { 0x043E, 0x041E },
+ { 0x043F, 0x041F },
+ { 0x0440, 0x0420 },
+ { 0x0441, 0x0421 },
+ { 0x0442, 0x0422 },
+ { 0x0443, 0x0423 },
+ { 0x0444, 0x0424 },
+ { 0x0445, 0x0425 },
+ { 0x0446, 0x0426 },
+ { 0x0447, 0x0427 },
+ { 0x0448, 0x0428 },
+ { 0x0449, 0x0429 },
+ { 0x044A, 0x042A },
+ { 0x044B, 0x042B },
+ { 0x044C, 0x042C },
+ { 0x044D, 0x042D },
+ { 0x044E, 0x042E },
+ { 0x044F, 0x042F },
+ { 0x0451, 0x0401 },
+ { 0x0452, 0x0402 },
+ { 0x0453, 0x0403 },
+ { 0x0454, 0x0404 },
+ { 0x0455, 0x0405 },
+ { 0x0456, 0x0406 },
+ { 0x0457, 0x0407 },
+ { 0x0458, 0x0408 },
+ { 0x0459, 0x0409 },
+ { 0x045A, 0x040A },
+ { 0x045B, 0x040B },
+ { 0x045C, 0x040C },
+ { 0x045E, 0x040E },
+ { 0x045F, 0x040F },
+ { 0x0461, 0x0460 },
+ { 0x0463, 0x0462 },
+ { 0x0465, 0x0464 },
+ { 0x0467, 0x0466 },
+ { 0x0469, 0x0468 },
+ { 0x046B, 0x046A },
+ { 0x046D, 0x046C },
+ { 0x046F, 0x046E },
+ { 0x0471, 0x0470 },
+ { 0x0473, 0x0472 },
+ { 0x0475, 0x0474 },
+ { 0x0477, 0x0476 },
+ { 0x0479, 0x0478 },
+ { 0x047B, 0x047A },
+ { 0x047D, 0x047C },
+ { 0x047F, 0x047E },
+ { 0x0481, 0x0480 },
+ { 0x0491, 0x0490 },
+ { 0x0493, 0x0492 },
+ { 0x0495, 0x0494 },
+ { 0x0497, 0x0496 },
+ { 0x0499, 0x0498 },
+ { 0x049B, 0x049A },
+ { 0x049D, 0x049C },
+ { 0x049F, 0x049E },
+ { 0x04A1, 0x04A0 },
+ { 0x04A3, 0x04A2 },
+ { 0x04A5, 0x04A4 },
+ { 0x04A7, 0x04A6 },
+ { 0x04A9, 0x04A8 },
+ { 0x04AB, 0x04AA },
+ { 0x04AD, 0x04AC },
+ { 0x04AF, 0x04AE },
+ { 0x04B1, 0x04B0 },
+ { 0x04B3, 0x04B2 },
+ { 0x04B5, 0x04B4 },
+ { 0x04B7, 0x04B6 },
+ { 0x04B9, 0x04B8 },
+ { 0x04BB, 0x04BA },
+ { 0x04BD, 0x04BC },
+ { 0x04BF, 0x04BE },
+ { 0x04C2, 0x04C1 },
+ { 0x04C4, 0x04C3 },
+ { 0x04C8, 0x04C7 },
+ { 0x04CC, 0x04CB },
+ { 0x04D1, 0x04D0 },
+ { 0x04D3, 0x04D2 },
+ { 0x04D5, 0x04D4 },
+ { 0x04D7, 0x04D6 },
+ { 0x04D9, 0x04D8 },
+ { 0x04DB, 0x04DA },
+ { 0x04DD, 0x04DC },
+ { 0x04DF, 0x04DE },
+ { 0x04E1, 0x04E0 },
+ { 0x04E3, 0x04E2 },
+ { 0x04E5, 0x04E4 },
+ { 0x04E7, 0x04E6 },
+ { 0x04E9, 0x04E8 },
+ { 0x04EB, 0x04EA },
+ { 0x04EF, 0x04EE },
+ { 0x04F1, 0x04F0 },
+ { 0x04F3, 0x04F2 },
+ { 0x04F5, 0x04F4 },
+ { 0x04F9, 0x04F8 },
+ { 0x0561, 0x0531 },
+ { 0x0562, 0x0532 },
+ { 0x0563, 0x0533 },
+ { 0x0564, 0x0534 },
+ { 0x0565, 0x0535 },
+ { 0x0566, 0x0536 },
+ { 0x0567, 0x0537 },
+ { 0x0568, 0x0538 },
+ { 0x0569, 0x0539 },
+ { 0x056A, 0x053A },
+ { 0x056B, 0x053B },
+ { 0x056C, 0x053C },
+ { 0x056D, 0x053D },
+ { 0x056E, 0x053E },
+ { 0x056F, 0x053F },
+ { 0x0570, 0x0540 },
+ { 0x0571, 0x0541 },
+ { 0x0572, 0x0542 },
+ { 0x0573, 0x0543 },
+ { 0x0574, 0x0544 },
+ { 0x0575, 0x0545 },
+ { 0x0576, 0x0546 },
+ { 0x0577, 0x0547 },
+ { 0x0578, 0x0548 },
+ { 0x0579, 0x0549 },
+ { 0x057A, 0x054A },
+ { 0x057B, 0x054B },
+ { 0x057C, 0x054C },
+ { 0x057D, 0x054D },
+ { 0x057E, 0x054E },
+ { 0x057F, 0x054F },
+ { 0x0580, 0x0550 },
+ { 0x0581, 0x0551 },
+ { 0x0582, 0x0552 },
+ { 0x0583, 0x0553 },
+ { 0x0584, 0x0554 },
+ { 0x0585, 0x0555 },
+ { 0x0586, 0x0556 },
+ { 0x10D0, 0x10A0 },
+ { 0x10D1, 0x10A1 },
+ { 0x10D2, 0x10A2 },
+ { 0x10D3, 0x10A3 },
+ { 0x10D4, 0x10A4 },
+ { 0x10D5, 0x10A5 },
+ { 0x10D6, 0x10A6 },
+ { 0x10D7, 0x10A7 },
+ { 0x10D8, 0x10A8 },
+ { 0x10D9, 0x10A9 },
+ { 0x10DA, 0x10AA },
+ { 0x10DB, 0x10AB },
+ { 0x10DC, 0x10AC },
+ { 0x10DD, 0x10AD },
+ { 0x10DE, 0x10AE },
+ { 0x10DF, 0x10AF },
+ { 0x10E0, 0x10B0 },
+ { 0x10E1, 0x10B1 },
+ { 0x10E2, 0x10B2 },
+ { 0x10E3, 0x10B3 },
+ { 0x10E4, 0x10B4 },
+ { 0x10E5, 0x10B5 },
+ { 0x10E6, 0x10B6 },
+ { 0x10E7, 0x10B7 },
+ { 0x10E8, 0x10B8 },
+ { 0x10E9, 0x10B9 },
+ { 0x10EA, 0x10BA },
+ { 0x10EB, 0x10BB },
+ { 0x10EC, 0x10BC },
+ { 0x10ED, 0x10BD },
+ { 0x10EE, 0x10BE },
+ { 0x10EF, 0x10BF },
+ { 0x10F0, 0x10C0 },
+ { 0x10F1, 0x10C1 },
+ { 0x10F2, 0x10C2 },
+ { 0x10F3, 0x10C3 },
+ { 0x10F4, 0x10C4 },
+ { 0x10F5, 0x10C5 },
+ { 0x1E01, 0x1E00 },
+ { 0x1E03, 0x1E02 },
+ { 0x1E05, 0x1E04 },
+ { 0x1E07, 0x1E06 },
+ { 0x1E09, 0x1E08 },
+ { 0x1E0B, 0x1E0A },
+ { 0x1E0D, 0x1E0C },
+ { 0x1E0F, 0x1E0E },
+ { 0x1E11, 0x1E10 },
+ { 0x1E13, 0x1E12 },
+ { 0x1E15, 0x1E14 },
+ { 0x1E17, 0x1E16 },
+ { 0x1E19, 0x1E18 },
+ { 0x1E1B, 0x1E1A },
+ { 0x1E1D, 0x1E1C },
+ { 0x1E1F, 0x1E1E },
+ { 0x1E21, 0x1E20 },
+ { 0x1E23, 0x1E22 },
+ { 0x1E25, 0x1E24 },
+ { 0x1E27, 0x1E26 },
+ { 0x1E29, 0x1E28 },
+ { 0x1E2B, 0x1E2A },
+ { 0x1E2D, 0x1E2C },
+ { 0x1E2F, 0x1E2E },
+ { 0x1E31, 0x1E30 },
+ { 0x1E33, 0x1E32 },
+ { 0x1E35, 0x1E34 },
+ { 0x1E37, 0x1E36 },
+ { 0x1E39, 0x1E38 },
+ { 0x1E3B, 0x1E3A },
+ { 0x1E3D, 0x1E3C },
+ { 0x1E3F, 0x1E3E },
+ { 0x1E41, 0x1E40 },
+ { 0x1E43, 0x1E42 },
+ { 0x1E45, 0x1E44 },
+ { 0x1E47, 0x1E46 },
+ { 0x1E49, 0x1E48 },
+ { 0x1E4B, 0x1E4A },
+ { 0x1E4D, 0x1E4C },
+ { 0x1E4F, 0x1E4E },
+ { 0x1E51, 0x1E50 },
+ { 0x1E53, 0x1E52 },
+ { 0x1E55, 0x1E54 },
+ { 0x1E57, 0x1E56 },
+ { 0x1E59, 0x1E58 },
+ { 0x1E5B, 0x1E5A },
+ { 0x1E5D, 0x1E5C },
+ { 0x1E5F, 0x1E5E },
+ { 0x1E61, 0x1E60 },
+ { 0x1E63, 0x1E62 },
+ { 0x1E65, 0x1E64 },
+ { 0x1E67, 0x1E66 },
+ { 0x1E69, 0x1E68 },
+ { 0x1E6B, 0x1E6A },
+ { 0x1E6D, 0x1E6C },
+ { 0x1E6F, 0x1E6E },
+ { 0x1E71, 0x1E70 },
+ { 0x1E73, 0x1E72 },
+ { 0x1E75, 0x1E74 },
+ { 0x1E77, 0x1E76 },
+ { 0x1E79, 0x1E78 },
+ { 0x1E7B, 0x1E7A },
+ { 0x1E7D, 0x1E7C },
+ { 0x1E7F, 0x1E7E },
+ { 0x1E81, 0x1E80 },
+ { 0x1E83, 0x1E82 },
+ { 0x1E85, 0x1E84 },
+ { 0x1E87, 0x1E86 },
+ { 0x1E89, 0x1E88 },
+ { 0x1E8B, 0x1E8A },
+ { 0x1E8D, 0x1E8C },
+ { 0x1E8F, 0x1E8E },
+ { 0x1E91, 0x1E90 },
+ { 0x1E93, 0x1E92 },
+ { 0x1E95, 0x1E94 },
+ { 0x1EA1, 0x1EA0 },
+ { 0x1EA3, 0x1EA2 },
+ { 0x1EA5, 0x1EA4 },
+ { 0x1EA7, 0x1EA6 },
+ { 0x1EA9, 0x1EA8 },
+ { 0x1EAB, 0x1EAA },
+ { 0x1EAD, 0x1EAC },
+ { 0x1EAF, 0x1EAE },
+ { 0x1EB1, 0x1EB0 },
+ { 0x1EB3, 0x1EB2 },
+ { 0x1EB5, 0x1EB4 },
+ { 0x1EB7, 0x1EB6 },
+ { 0x1EB9, 0x1EB8 },
+ { 0x1EBB, 0x1EBA },
+ { 0x1EBD, 0x1EBC },
+ { 0x1EBF, 0x1EBE },
+ { 0x1EC1, 0x1EC0 },
+ { 0x1EC3, 0x1EC2 },
+ { 0x1EC5, 0x1EC4 },
+ { 0x1EC7, 0x1EC6 },
+ { 0x1EC9, 0x1EC8 },
+ { 0x1ECB, 0x1ECA },
+ { 0x1ECD, 0x1ECC },
+ { 0x1ECF, 0x1ECE },
+ { 0x1ED1, 0x1ED0 },
+ { 0x1ED3, 0x1ED2 },
+ { 0x1ED5, 0x1ED4 },
+ { 0x1ED7, 0x1ED6 },
+ { 0x1ED9, 0x1ED8 },
+ { 0x1EDB, 0x1EDA },
+ { 0x1EDD, 0x1EDC },
+ { 0x1EDF, 0x1EDE },
+ { 0x1EE1, 0x1EE0 },
+ { 0x1EE3, 0x1EE2 },
+ { 0x1EE5, 0x1EE4 },
+ { 0x1EE7, 0x1EE6 },
+ { 0x1EE9, 0x1EE8 },
+ { 0x1EEB, 0x1EEA },
+ { 0x1EED, 0x1EEC },
+ { 0x1EEF, 0x1EEE },
+ { 0x1EF1, 0x1EF0 },
+ { 0x1EF3, 0x1EF2 },
+ { 0x1EF5, 0x1EF4 },
+ { 0x1EF7, 0x1EF6 },
+ { 0x1EF9, 0x1EF8 },
+ { 0x1F00, 0x1F08 },
+ { 0x1F01, 0x1F09 },
+ { 0x1F02, 0x1F0A },
+ { 0x1F03, 0x1F0B },
+ { 0x1F04, 0x1F0C },
+ { 0x1F05, 0x1F0D },
+ { 0x1F06, 0x1F0E },
+ { 0x1F07, 0x1F0F },
+ { 0x1F10, 0x1F18 },
+ { 0x1F11, 0x1F19 },
+ { 0x1F12, 0x1F1A },
+ { 0x1F13, 0x1F1B },
+ { 0x1F14, 0x1F1C },
+ { 0x1F15, 0x1F1D },
+ { 0x1F20, 0x1F28 },
+ { 0x1F21, 0x1F29 },
+ { 0x1F22, 0x1F2A },
+ { 0x1F23, 0x1F2B },
+ { 0x1F24, 0x1F2C },
+ { 0x1F25, 0x1F2D },
+ { 0x1F26, 0x1F2E },
+ { 0x1F27, 0x1F2F },
+ { 0x1F30, 0x1F38 },
+ { 0x1F31, 0x1F39 },
+ { 0x1F32, 0x1F3A },
+ { 0x1F33, 0x1F3B },
+ { 0x1F34, 0x1F3C },
+ { 0x1F35, 0x1F3D },
+ { 0x1F36, 0x1F3E },
+ { 0x1F37, 0x1F3F },
+ { 0x1F40, 0x1F48 },
+ { 0x1F41, 0x1F49 },
+ { 0x1F42, 0x1F4A },
+ { 0x1F43, 0x1F4B },
+ { 0x1F44, 0x1F4C },
+ { 0x1F45, 0x1F4D },
+ { 0x1F51, 0x1F59 },
+ { 0x1F53, 0x1F5B },
+ { 0x1F55, 0x1F5D },
+ { 0x1F57, 0x1F5F },
+ { 0x1F60, 0x1F68 },
+ { 0x1F61, 0x1F69 },
+ { 0x1F62, 0x1F6A },
+ { 0x1F63, 0x1F6B },
+ { 0x1F64, 0x1F6C },
+ { 0x1F65, 0x1F6D },
+ { 0x1F66, 0x1F6E },
+ { 0x1F67, 0x1F6F },
+ { 0x1F80, 0x1F88 },
+ { 0x1F81, 0x1F89 },
+ { 0x1F82, 0x1F8A },
+ { 0x1F83, 0x1F8B },
+ { 0x1F84, 0x1F8C },
+ { 0x1F85, 0x1F8D },
+ { 0x1F86, 0x1F8E },
+ { 0x1F87, 0x1F8F },
+ { 0x1F90, 0x1F98 },
+ { 0x1F91, 0x1F99 },
+ { 0x1F92, 0x1F9A },
+ { 0x1F93, 0x1F9B },
+ { 0x1F94, 0x1F9C },
+ { 0x1F95, 0x1F9D },
+ { 0x1F96, 0x1F9E },
+ { 0x1F97, 0x1F9F },
+ { 0x1FA0, 0x1FA8 },
+ { 0x1FA1, 0x1FA9 },
+ { 0x1FA2, 0x1FAA },
+ { 0x1FA3, 0x1FAB },
+ { 0x1FA4, 0x1FAC },
+ { 0x1FA5, 0x1FAD },
+ { 0x1FA6, 0x1FAE },
+ { 0x1FA7, 0x1FAF },
+ { 0x1FB0, 0x1FB8 },
+ { 0x1FB1, 0x1FB9 },
+ { 0x1FD0, 0x1FD8 },
+ { 0x1FD1, 0x1FD9 },
+ { 0x1FE0, 0x1FE8 },
+ { 0x1FE1, 0x1FE9 },
+ { 0x24D0, 0x24B6 },
+ { 0x24D1, 0x24B7 },
+ { 0x24D2, 0x24B8 },
+ { 0x24D3, 0x24B9 },
+ { 0x24D4, 0x24BA },
+ { 0x24D5, 0x24BB },
+ { 0x24D6, 0x24BC },
+ { 0x24D7, 0x24BD },
+ { 0x24D8, 0x24BE },
+ { 0x24D9, 0x24BF },
+ { 0x24DA, 0x24C0 },
+ { 0x24DB, 0x24C1 },
+ { 0x24DC, 0x24C2 },
+ { 0x24DD, 0x24C3 },
+ { 0x24DE, 0x24C4 },
+ { 0x24DF, 0x24C5 },
+ { 0x24E0, 0x24C6 },
+ { 0x24E1, 0x24C7 },
+ { 0x24E2, 0x24C8 },
+ { 0x24E3, 0x24C9 },
+ { 0x24E4, 0x24CA },
+ { 0x24E5, 0x24CB },
+ { 0x24E6, 0x24CC },
+ { 0x24E7, 0x24CD },
+ { 0x24E8, 0x24CE },
+ { 0x24E9, 0x24CF },
+ { 0xFF41, 0xFF21 },
+ { 0xFF42, 0xFF22 },
+ { 0xFF43, 0xFF23 },
+ { 0xFF44, 0xFF24 },
+ { 0xFF45, 0xFF25 },
+ { 0xFF46, 0xFF26 },
+ { 0xFF47, 0xFF27 },
+ { 0xFF48, 0xFF28 },
+ { 0xFF49, 0xFF29 },
+ { 0xFF4A, 0xFF2A },
+ { 0xFF4B, 0xFF2B },
+ { 0xFF4C, 0xFF2C },
+ { 0xFF4D, 0xFF2D },
+ { 0xFF4E, 0xFF2E },
+ { 0xFF4F, 0xFF2F },
+ { 0xFF50, 0xFF30 },
+ { 0xFF51, 0xFF31 },
+ { 0xFF52, 0xFF32 },
+ { 0xFF53, 0xFF33 },
+ { 0xFF54, 0xFF34 },
+ { 0xFF55, 0xFF35 },
+ { 0xFF56, 0xFF36 },
+ { 0xFF57, 0xFF37 },
+ { 0xFF58, 0xFF38 },
+ { 0xFF59, 0xFF39 },
+ { 0xFF5A, 0xFF3A },
+};
+
+static const int reverse_caps_table[CAPS_LEN - 1][2] = {
+ { 0x0041, 0x0061 },
+ { 0x0042, 0x0062 },
+ { 0x0043, 0x0063 },
+ { 0x0044, 0x0064 },
+ { 0x0045, 0x0065 },
+ { 0x0046, 0x0066 },
+ { 0x0047, 0x0067 },
+ { 0x0048, 0x0068 },
+ { 0x0049, 0x0069 },
+ // { 0x0049, 0x0131 }, // dotless I
+ { 0x004A, 0x006A },
+ { 0x004B, 0x006B },
+ { 0x004C, 0x006C },
+ { 0x004D, 0x006D },
+ { 0x004E, 0x006E },
+ { 0x004F, 0x006F },
+ { 0x0050, 0x0070 },
+ { 0x0051, 0x0071 },
+ { 0x0052, 0x0072 },
+ { 0x0053, 0x0073 },
+ { 0x0054, 0x0074 },
+ { 0x0055, 0x0075 },
+ { 0x0056, 0x0076 },
+ { 0x0057, 0x0077 },
+ { 0x0058, 0x0078 },
+ { 0x0059, 0x0079 },
+ { 0x005A, 0x007A },
+ { 0x00C0, 0x00E0 },
+ { 0x00C1, 0x00E1 },
+ { 0x00C2, 0x00E2 },
+ { 0x00C3, 0x00E3 },
+ { 0x00C4, 0x00E4 },
+ { 0x00C5, 0x00E5 },
+ { 0x00C6, 0x00E6 },
+ { 0x00C7, 0x00E7 },
+ { 0x00C8, 0x00E8 },
+ { 0x00C9, 0x00E9 },
+ { 0x00CA, 0x00EA },
+ { 0x00CB, 0x00EB },
+ { 0x00CC, 0x00EC },
+ { 0x00CD, 0x00ED },
+ { 0x00CE, 0x00EE },
+ { 0x00CF, 0x00EF },
+ { 0x00D0, 0x00F0 },
+ { 0x00D1, 0x00F1 },
+ { 0x00D2, 0x00F2 },
+ { 0x00D3, 0x00F3 },
+ { 0x00D4, 0x00F4 },
+ { 0x00D5, 0x00F5 },
+ { 0x00D6, 0x00F6 },
+ { 0x00D8, 0x00F8 },
+ { 0x00D9, 0x00F9 },
+ { 0x00DA, 0x00FA },
+ { 0x00DB, 0x00FB },
+ { 0x00DC, 0x00FC },
+ { 0x00DD, 0x00FD },
+ { 0x00DE, 0x00FE },
+ { 0x0100, 0x0101 },
+ { 0x0102, 0x0103 },
+ { 0x0104, 0x0105 },
+ { 0x0106, 0x0107 },
+ { 0x0108, 0x0109 },
+ { 0x010A, 0x010B },
+ { 0x010C, 0x010D },
+ { 0x010E, 0x010F },
+ { 0x0110, 0x0111 },
+ { 0x0112, 0x0113 },
+ { 0x0114, 0x0115 },
+ { 0x0116, 0x0117 },
+ { 0x0118, 0x0119 },
+ { 0x011A, 0x011B },
+ { 0x011C, 0x011D },
+ { 0x011E, 0x011F },
+ { 0x0120, 0x0121 },
+ { 0x0122, 0x0123 },
+ { 0x0124, 0x0125 },
+ { 0x0126, 0x0127 },
+ { 0x0128, 0x0129 },
+ { 0x012A, 0x012B },
+ { 0x012C, 0x012D },
+ { 0x012E, 0x012F },
+ { 0x0132, 0x0133 },
+ { 0x0134, 0x0135 },
+ { 0x0136, 0x0137 },
+ { 0x0139, 0x013A },
+ { 0x013B, 0x013C },
+ { 0x013D, 0x013E },
+ { 0x013F, 0x0140 },
+ { 0x0141, 0x0142 },
+ { 0x0143, 0x0144 },
+ { 0x0145, 0x0146 },
+ { 0x0147, 0x0148 },
+ { 0x014A, 0x014B },
+ { 0x014C, 0x014D },
+ { 0x014E, 0x014F },
+ { 0x0150, 0x0151 },
+ { 0x0152, 0x0153 },
+ { 0x0154, 0x0155 },
+ { 0x0156, 0x0157 },
+ { 0x0158, 0x0159 },
+ { 0x015A, 0x015B },
+ { 0x015C, 0x015D },
+ { 0x015E, 0x015F },
+ { 0x0160, 0x0161 },
+ { 0x0162, 0x0163 },
+ { 0x0164, 0x0165 },
+ { 0x0166, 0x0167 },
+ { 0x0168, 0x0169 },
+ { 0x016A, 0x016B },
+ { 0x016C, 0x016D },
+ { 0x016E, 0x016F },
+ { 0x0170, 0x0171 },
+ { 0x0172, 0x0173 },
+ { 0x0174, 0x0175 },
+ { 0x0176, 0x0177 },
+ { 0x0178, 0x00FF },
+ { 0x0179, 0x017A },
+ { 0x017B, 0x017C },
+ { 0x017D, 0x017E },
+ { 0x0181, 0x0253 },
+ { 0x0182, 0x0183 },
+ { 0x0184, 0x0185 },
+ { 0x0186, 0x0254 },
+ { 0x0187, 0x0188 },
+ { 0x018A, 0x0257 },
+ { 0x018B, 0x018C },
+ { 0x018E, 0x0258 },
+ { 0x018F, 0x0259 },
+ { 0x0190, 0x025B },
+ { 0x0191, 0x0192 },
+ { 0x0193, 0x0260 },
+ { 0x0194, 0x0263 },
+ { 0x0196, 0x0269 },
+ { 0x0197, 0x0268 },
+ { 0x0198, 0x0199 },
+ { 0x019C, 0x026F },
+ { 0x019D, 0x0272 },
+ { 0x019F, 0x0275 },
+ { 0x01A0, 0x01A1 },
+ { 0x01A2, 0x01A3 },
+ { 0x01A4, 0x01A5 },
+ { 0x01A7, 0x01A8 },
+ { 0x01A9, 0x0283 },
+ { 0x01AC, 0x01AD },
+ { 0x01AE, 0x0288 },
+ { 0x01AF, 0x01B0 },
+ { 0x01B1, 0x028A },
+ { 0x01B2, 0x028B },
+ { 0x01B3, 0x01B4 },
+ { 0x01B5, 0x01B6 },
+ { 0x01B7, 0x0292 },
+ { 0x01B8, 0x01B9 },
+ { 0x01BC, 0x01BD },
+ { 0x01C4, 0x01C6 },
+ { 0x01C7, 0x01C9 },
+ { 0x01CA, 0x01CC },
+ { 0x01CD, 0x01CE },
+ { 0x01CF, 0x01D0 },
+ { 0x01D1, 0x01D2 },
+ { 0x01D3, 0x01D4 },
+ { 0x01D5, 0x01D6 },
+ { 0x01D7, 0x01D8 },
+ { 0x01D9, 0x01DA },
+ { 0x01DB, 0x01DC },
+ { 0x01DE, 0x01DF },
+ { 0x01E0, 0x01E1 },
+ { 0x01E2, 0x01E3 },
+ { 0x01E4, 0x01E5 },
+ { 0x01E6, 0x01E7 },
+ { 0x01E8, 0x01E9 },
+ { 0x01EA, 0x01EB },
+ { 0x01EC, 0x01ED },
+ { 0x01EE, 0x01EF },
+ { 0x01F1, 0x01F3 },
+ { 0x01F4, 0x01F5 },
+ { 0x01FA, 0x01FB },
+ { 0x01FC, 0x01FD },
+ { 0x01FE, 0x01FF },
+ { 0x0200, 0x0201 },
+ { 0x0202, 0x0203 },
+ { 0x0204, 0x0205 },
+ { 0x0206, 0x0207 },
+ { 0x0208, 0x0209 },
+ { 0x020A, 0x020B },
+ { 0x020C, 0x020D },
+ { 0x020E, 0x020F },
+ { 0x0210, 0x0211 },
+ { 0x0212, 0x0213 },
+ { 0x0214, 0x0215 },
+ { 0x0216, 0x0217 },
+ { 0x0386, 0x03AC },
+ { 0x0388, 0x03AD },
+ { 0x0389, 0x03AE },
+ { 0x038A, 0x03AF },
+ { 0x038C, 0x03CC },
+ { 0x038E, 0x03CD },
+ { 0x038F, 0x03CE },
+ { 0x0391, 0x03B1 },
+ { 0x0392, 0x03B2 },
+ { 0x0393, 0x03B3 },
+ { 0x0394, 0x03B4 },
+ { 0x0395, 0x03B5 },
+ { 0x0396, 0x03B6 },
+ { 0x0397, 0x03B7 },
+ { 0x0398, 0x03B8 },
+ { 0x0399, 0x03B9 },
+ { 0x039A, 0x03BA },
+ { 0x039B, 0x03BB },
+ { 0x039C, 0x03BC },
+ { 0x039D, 0x03BD },
+ { 0x039E, 0x03BE },
+ { 0x039F, 0x03BF },
+ { 0x03A0, 0x03C0 },
+ { 0x03A1, 0x03C1 },
+ { 0x03A3, 0x03C3 },
+ { 0x03A4, 0x03C4 },
+ { 0x03A5, 0x03C5 },
+ { 0x03A6, 0x03C6 },
+ { 0x03A7, 0x03C7 },
+ { 0x03A8, 0x03C8 },
+ { 0x03A9, 0x03C9 },
+ { 0x03AA, 0x03CA },
+ { 0x03AB, 0x03CB },
+ { 0x03E2, 0x03E3 },
+ { 0x03E4, 0x03E5 },
+ { 0x03E6, 0x03E7 },
+ { 0x03E8, 0x03E9 },
+ { 0x03EA, 0x03EB },
+ { 0x03EC, 0x03ED },
+ { 0x03EE, 0x03EF },
+ { 0x0401, 0x0451 },
+ { 0x0402, 0x0452 },
+ { 0x0403, 0x0453 },
+ { 0x0404, 0x0454 },
+ { 0x0405, 0x0455 },
+ { 0x0406, 0x0456 },
+ { 0x0407, 0x0457 },
+ { 0x0408, 0x0458 },
+ { 0x0409, 0x0459 },
+ { 0x040A, 0x045A },
+ { 0x040B, 0x045B },
+ { 0x040C, 0x045C },
+ { 0x040E, 0x045E },
+ { 0x040F, 0x045F },
+ { 0x0410, 0x0430 },
+ { 0x0411, 0x0431 },
+ { 0x0412, 0x0432 },
+ { 0x0413, 0x0433 },
+ { 0x0414, 0x0434 },
+ { 0x0415, 0x0435 },
+ { 0x0416, 0x0436 },
+ { 0x0417, 0x0437 },
+ { 0x0418, 0x0438 },
+ { 0x0419, 0x0439 },
+ { 0x041A, 0x043A },
+ { 0x041B, 0x043B },
+ { 0x041C, 0x043C },
+ { 0x041D, 0x043D },
+ { 0x041E, 0x043E },
+ { 0x041F, 0x043F },
+ { 0x0420, 0x0440 },
+ { 0x0421, 0x0441 },
+ { 0x0422, 0x0442 },
+ { 0x0423, 0x0443 },
+ { 0x0424, 0x0444 },
+ { 0x0425, 0x0445 },
+ { 0x0426, 0x0446 },
+ { 0x0427, 0x0447 },
+ { 0x0428, 0x0448 },
+ { 0x0429, 0x0449 },
+ { 0x042A, 0x044A },
+ { 0x042B, 0x044B },
+ { 0x042C, 0x044C },
+ { 0x042D, 0x044D },
+ { 0x042E, 0x044E },
+ { 0x042F, 0x044F },
+ { 0x0460, 0x0461 },
+ { 0x0462, 0x0463 },
+ { 0x0464, 0x0465 },
+ { 0x0466, 0x0467 },
+ { 0x0468, 0x0469 },
+ { 0x046A, 0x046B },
+ { 0x046C, 0x046D },
+ { 0x046E, 0x046F },
+ { 0x0470, 0x0471 },
+ { 0x0472, 0x0473 },
+ { 0x0474, 0x0475 },
+ { 0x0476, 0x0477 },
+ { 0x0478, 0x0479 },
+ { 0x047A, 0x047B },
+ { 0x047C, 0x047D },
+ { 0x047E, 0x047F },
+ { 0x0480, 0x0481 },
+ { 0x0490, 0x0491 },
+ { 0x0492, 0x0493 },
+ { 0x0494, 0x0495 },
+ { 0x0496, 0x0497 },
+ { 0x0498, 0x0499 },
+ { 0x049A, 0x049B },
+ { 0x049C, 0x049D },
+ { 0x049E, 0x049F },
+ { 0x04A0, 0x04A1 },
+ { 0x04A2, 0x04A3 },
+ { 0x04A4, 0x04A5 },
+ { 0x04A6, 0x04A7 },
+ { 0x04A8, 0x04A9 },
+ { 0x04AA, 0x04AB },
+ { 0x04AC, 0x04AD },
+ { 0x04AE, 0x04AF },
+ { 0x04B0, 0x04B1 },
+ { 0x04B2, 0x04B3 },
+ { 0x04B4, 0x04B5 },
+ { 0x04B6, 0x04B7 },
+ { 0x04B8, 0x04B9 },
+ { 0x04BA, 0x04BB },
+ { 0x04BC, 0x04BD },
+ { 0x04BE, 0x04BF },
+ { 0x04C1, 0x04C2 },
+ { 0x04C3, 0x04C4 },
+ { 0x04C7, 0x04C8 },
+ { 0x04CB, 0x04CC },
+ { 0x04D0, 0x04D1 },
+ { 0x04D2, 0x04D3 },
+ { 0x04D4, 0x04D5 },
+ { 0x04D6, 0x04D7 },
+ { 0x04D8, 0x04D9 },
+ { 0x04DA, 0x04DB },
+ { 0x04DC, 0x04DD },
+ { 0x04DE, 0x04DF },
+ { 0x04E0, 0x04E1 },
+ { 0x04E2, 0x04E3 },
+ { 0x04E4, 0x04E5 },
+ { 0x04E6, 0x04E7 },
+ { 0x04E8, 0x04E9 },
+ { 0x04EA, 0x04EB },
+ { 0x04EE, 0x04EF },
+ { 0x04F0, 0x04F1 },
+ { 0x04F2, 0x04F3 },
+ { 0x04F4, 0x04F5 },
+ { 0x04F8, 0x04F9 },
+ { 0x0531, 0x0561 },
+ { 0x0532, 0x0562 },
+ { 0x0533, 0x0563 },
+ { 0x0534, 0x0564 },
+ { 0x0535, 0x0565 },
+ { 0x0536, 0x0566 },
+ { 0x0537, 0x0567 },
+ { 0x0538, 0x0568 },
+ { 0x0539, 0x0569 },
+ { 0x053A, 0x056A },
+ { 0x053B, 0x056B },
+ { 0x053C, 0x056C },
+ { 0x053D, 0x056D },
+ { 0x053E, 0x056E },
+ { 0x053F, 0x056F },
+ { 0x0540, 0x0570 },
+ { 0x0541, 0x0571 },
+ { 0x0542, 0x0572 },
+ { 0x0543, 0x0573 },
+ { 0x0544, 0x0574 },
+ { 0x0545, 0x0575 },
+ { 0x0546, 0x0576 },
+ { 0x0547, 0x0577 },
+ { 0x0548, 0x0578 },
+ { 0x0549, 0x0579 },
+ { 0x054A, 0x057A },
+ { 0x054B, 0x057B },
+ { 0x054C, 0x057C },
+ { 0x054D, 0x057D },
+ { 0x054E, 0x057E },
+ { 0x054F, 0x057F },
+ { 0x0550, 0x0580 },
+ { 0x0551, 0x0581 },
+ { 0x0552, 0x0582 },
+ { 0x0553, 0x0583 },
+ { 0x0554, 0x0584 },
+ { 0x0555, 0x0585 },
+ { 0x0556, 0x0586 },
+ { 0x10A0, 0x10D0 },
+ { 0x10A1, 0x10D1 },
+ { 0x10A2, 0x10D2 },
+ { 0x10A3, 0x10D3 },
+ { 0x10A4, 0x10D4 },
+ { 0x10A5, 0x10D5 },
+ { 0x10A6, 0x10D6 },
+ { 0x10A7, 0x10D7 },
+ { 0x10A8, 0x10D8 },
+ { 0x10A9, 0x10D9 },
+ { 0x10AA, 0x10DA },
+ { 0x10AB, 0x10DB },
+ { 0x10AC, 0x10DC },
+ { 0x10AD, 0x10DD },
+ { 0x10AE, 0x10DE },
+ { 0x10AF, 0x10DF },
+ { 0x10B0, 0x10E0 },
+ { 0x10B1, 0x10E1 },
+ { 0x10B2, 0x10E2 },
+ { 0x10B3, 0x10E3 },
+ { 0x10B4, 0x10E4 },
+ { 0x10B5, 0x10E5 },
+ { 0x10B6, 0x10E6 },
+ { 0x10B7, 0x10E7 },
+ { 0x10B8, 0x10E8 },
+ { 0x10B9, 0x10E9 },
+ { 0x10BA, 0x10EA },
+ { 0x10BB, 0x10EB },
+ { 0x10BC, 0x10EC },
+ { 0x10BD, 0x10ED },
+ { 0x10BE, 0x10EE },
+ { 0x10BF, 0x10EF },
+ { 0x10C0, 0x10F0 },
+ { 0x10C1, 0x10F1 },
+ { 0x10C2, 0x10F2 },
+ { 0x10C3, 0x10F3 },
+ { 0x10C4, 0x10F4 },
+ { 0x10C5, 0x10F5 },
+ { 0x1E00, 0x1E01 },
+ { 0x1E02, 0x1E03 },
+ { 0x1E04, 0x1E05 },
+ { 0x1E06, 0x1E07 },
+ { 0x1E08, 0x1E09 },
+ { 0x1E0A, 0x1E0B },
+ { 0x1E0C, 0x1E0D },
+ { 0x1E0E, 0x1E0F },
+ { 0x1E10, 0x1E11 },
+ { 0x1E12, 0x1E13 },
+ { 0x1E14, 0x1E15 },
+ { 0x1E16, 0x1E17 },
+ { 0x1E18, 0x1E19 },
+ { 0x1E1A, 0x1E1B },
+ { 0x1E1C, 0x1E1D },
+ { 0x1E1E, 0x1E1F },
+ { 0x1E20, 0x1E21 },
+ { 0x1E22, 0x1E23 },
+ { 0x1E24, 0x1E25 },
+ { 0x1E26, 0x1E27 },
+ { 0x1E28, 0x1E29 },
+ { 0x1E2A, 0x1E2B },
+ { 0x1E2C, 0x1E2D },
+ { 0x1E2E, 0x1E2F },
+ { 0x1E30, 0x1E31 },
+ { 0x1E32, 0x1E33 },
+ { 0x1E34, 0x1E35 },
+ { 0x1E36, 0x1E37 },
+ { 0x1E38, 0x1E39 },
+ { 0x1E3A, 0x1E3B },
+ { 0x1E3C, 0x1E3D },
+ { 0x1E3E, 0x1E3F },
+ { 0x1E40, 0x1E41 },
+ { 0x1E42, 0x1E43 },
+ { 0x1E44, 0x1E45 },
+ { 0x1E46, 0x1E47 },
+ { 0x1E48, 0x1E49 },
+ { 0x1E4A, 0x1E4B },
+ { 0x1E4C, 0x1E4D },
+ { 0x1E4E, 0x1E4F },
+ { 0x1E50, 0x1E51 },
+ { 0x1E52, 0x1E53 },
+ { 0x1E54, 0x1E55 },
+ { 0x1E56, 0x1E57 },
+ { 0x1E58, 0x1E59 },
+ { 0x1E5A, 0x1E5B },
+ { 0x1E5C, 0x1E5D },
+ { 0x1E5E, 0x1E5F },
+ { 0x1E60, 0x1E61 },
+ { 0x1E62, 0x1E63 },
+ { 0x1E64, 0x1E65 },
+ { 0x1E66, 0x1E67 },
+ { 0x1E68, 0x1E69 },
+ { 0x1E6A, 0x1E6B },
+ { 0x1E6C, 0x1E6D },
+ { 0x1E6E, 0x1E6F },
+ { 0x1E70, 0x1E71 },
+ { 0x1E72, 0x1E73 },
+ { 0x1E74, 0x1E75 },
+ { 0x1E76, 0x1E77 },
+ { 0x1E78, 0x1E79 },
+ { 0x1E7A, 0x1E7B },
+ { 0x1E7C, 0x1E7D },
+ { 0x1E7E, 0x1E7F },
+ { 0x1E80, 0x1E81 },
+ { 0x1E82, 0x1E83 },
+ { 0x1E84, 0x1E85 },
+ { 0x1E86, 0x1E87 },
+ { 0x1E88, 0x1E89 },
+ { 0x1E8A, 0x1E8B },
+ { 0x1E8C, 0x1E8D },
+ { 0x1E8E, 0x1E8F },
+ { 0x1E90, 0x1E91 },
+ { 0x1E92, 0x1E93 },
+ { 0x1E94, 0x1E95 },
+ { 0x1EA0, 0x1EA1 },
+ { 0x1EA2, 0x1EA3 },
+ { 0x1EA4, 0x1EA5 },
+ { 0x1EA6, 0x1EA7 },
+ { 0x1EA8, 0x1EA9 },
+ { 0x1EAA, 0x1EAB },
+ { 0x1EAC, 0x1EAD },
+ { 0x1EAE, 0x1EAF },
+ { 0x1EB0, 0x1EB1 },
+ { 0x1EB2, 0x1EB3 },
+ { 0x1EB4, 0x1EB5 },
+ { 0x1EB6, 0x1EB7 },
+ { 0x1EB8, 0x1EB9 },
+ { 0x1EBA, 0x1EBB },
+ { 0x1EBC, 0x1EBD },
+ { 0x1EBE, 0x1EBF },
+ { 0x1EC0, 0x1EC1 },
+ { 0x1EC2, 0x1EC3 },
+ { 0x1EC4, 0x1EC5 },
+ { 0x1EC6, 0x1EC7 },
+ { 0x1EC8, 0x1EC9 },
+ { 0x1ECA, 0x1ECB },
+ { 0x1ECC, 0x1ECD },
+ { 0x1ECE, 0x1ECF },
+ { 0x1ED0, 0x1ED1 },
+ { 0x1ED2, 0x1ED3 },
+ { 0x1ED4, 0x1ED5 },
+ { 0x1ED6, 0x1ED7 },
+ { 0x1ED8, 0x1ED9 },
+ { 0x1EDA, 0x1EDB },
+ { 0x1EDC, 0x1EDD },
+ { 0x1EDE, 0x1EDF },
+ { 0x1EE0, 0x1EE1 },
+ { 0x1EE2, 0x1EE3 },
+ { 0x1EE4, 0x1EE5 },
+ { 0x1EE6, 0x1EE7 },
+ { 0x1EE8, 0x1EE9 },
+ { 0x1EEA, 0x1EEB },
+ { 0x1EEC, 0x1EED },
+ { 0x1EEE, 0x1EEF },
+ { 0x1EF0, 0x1EF1 },
+ { 0x1EF2, 0x1EF3 },
+ { 0x1EF4, 0x1EF5 },
+ { 0x1EF6, 0x1EF7 },
+ { 0x1EF8, 0x1EF9 },
+ { 0x1F08, 0x1F00 },
+ { 0x1F09, 0x1F01 },
+ { 0x1F0A, 0x1F02 },
+ { 0x1F0B, 0x1F03 },
+ { 0x1F0C, 0x1F04 },
+ { 0x1F0D, 0x1F05 },
+ { 0x1F0E, 0x1F06 },
+ { 0x1F0F, 0x1F07 },
+ { 0x1F18, 0x1F10 },
+ { 0x1F19, 0x1F11 },
+ { 0x1F1A, 0x1F12 },
+ { 0x1F1B, 0x1F13 },
+ { 0x1F1C, 0x1F14 },
+ { 0x1F1D, 0x1F15 },
+ { 0x1F28, 0x1F20 },
+ { 0x1F29, 0x1F21 },
+ { 0x1F2A, 0x1F22 },
+ { 0x1F2B, 0x1F23 },
+ { 0x1F2C, 0x1F24 },
+ { 0x1F2D, 0x1F25 },
+ { 0x1F2E, 0x1F26 },
+ { 0x1F2F, 0x1F27 },
+ { 0x1F38, 0x1F30 },
+ { 0x1F39, 0x1F31 },
+ { 0x1F3A, 0x1F32 },
+ { 0x1F3B, 0x1F33 },
+ { 0x1F3C, 0x1F34 },
+ { 0x1F3D, 0x1F35 },
+ { 0x1F3E, 0x1F36 },
+ { 0x1F3F, 0x1F37 },
+ { 0x1F48, 0x1F40 },
+ { 0x1F49, 0x1F41 },
+ { 0x1F4A, 0x1F42 },
+ { 0x1F4B, 0x1F43 },
+ { 0x1F4C, 0x1F44 },
+ { 0x1F4D, 0x1F45 },
+ { 0x1F59, 0x1F51 },
+ { 0x1F5B, 0x1F53 },
+ { 0x1F5D, 0x1F55 },
+ { 0x1F5F, 0x1F57 },
+ { 0x1F68, 0x1F60 },
+ { 0x1F69, 0x1F61 },
+ { 0x1F6A, 0x1F62 },
+ { 0x1F6B, 0x1F63 },
+ { 0x1F6C, 0x1F64 },
+ { 0x1F6D, 0x1F65 },
+ { 0x1F6E, 0x1F66 },
+ { 0x1F6F, 0x1F67 },
+ { 0x1F88, 0x1F80 },
+ { 0x1F89, 0x1F81 },
+ { 0x1F8A, 0x1F82 },
+ { 0x1F8B, 0x1F83 },
+ { 0x1F8C, 0x1F84 },
+ { 0x1F8D, 0x1F85 },
+ { 0x1F8E, 0x1F86 },
+ { 0x1F8F, 0x1F87 },
+ { 0x1F98, 0x1F90 },
+ { 0x1F99, 0x1F91 },
+ { 0x1F9A, 0x1F92 },
+ { 0x1F9B, 0x1F93 },
+ { 0x1F9C, 0x1F94 },
+ { 0x1F9D, 0x1F95 },
+ { 0x1F9E, 0x1F96 },
+ { 0x1F9F, 0x1F97 },
+ { 0x1FA8, 0x1FA0 },
+ { 0x1FA9, 0x1FA1 },
+ { 0x1FAA, 0x1FA2 },
+ { 0x1FAB, 0x1FA3 },
+ { 0x1FAC, 0x1FA4 },
+ { 0x1FAD, 0x1FA5 },
+ { 0x1FAE, 0x1FA6 },
+ { 0x1FAF, 0x1FA7 },
+ { 0x1FB8, 0x1FB0 },
+ { 0x1FB9, 0x1FB1 },
+ { 0x1FD8, 0x1FD0 },
+ { 0x1FD9, 0x1FD1 },
+ { 0x1FE8, 0x1FE0 },
+ { 0x1FE9, 0x1FE1 },
+ { 0x24B6, 0x24D0 },
+ { 0x24B7, 0x24D1 },
+ { 0x24B8, 0x24D2 },
+ { 0x24B9, 0x24D3 },
+ { 0x24BA, 0x24D4 },
+ { 0x24BB, 0x24D5 },
+ { 0x24BC, 0x24D6 },
+ { 0x24BD, 0x24D7 },
+ { 0x24BE, 0x24D8 },
+ { 0x24BF, 0x24D9 },
+ { 0x24C0, 0x24DA },
+ { 0x24C1, 0x24DB },
+ { 0x24C2, 0x24DC },
+ { 0x24C3, 0x24DD },
+ { 0x24C4, 0x24DE },
+ { 0x24C5, 0x24DF },
+ { 0x24C6, 0x24E0 },
+ { 0x24C7, 0x24E1 },
+ { 0x24C8, 0x24E2 },
+ { 0x24C9, 0x24E3 },
+ { 0x24CA, 0x24E4 },
+ { 0x24CB, 0x24E5 },
+ { 0x24CC, 0x24E6 },
+ { 0x24CD, 0x24E7 },
+ { 0x24CE, 0x24E8 },
+ { 0x24CF, 0x24E9 },
+ { 0xFF21, 0xFF41 },
+ { 0xFF22, 0xFF42 },
+ { 0xFF23, 0xFF43 },
+ { 0xFF24, 0xFF44 },
+ { 0xFF25, 0xFF45 },
+ { 0xFF26, 0xFF46 },
+ { 0xFF27, 0xFF47 },
+ { 0xFF28, 0xFF48 },
+ { 0xFF29, 0xFF49 },
+ { 0xFF2A, 0xFF4A },
+ { 0xFF2B, 0xFF4B },
+ { 0xFF2C, 0xFF4C },
+ { 0xFF2D, 0xFF4D },
+ { 0xFF2E, 0xFF4E },
+ { 0xFF2F, 0xFF4F },
+ { 0xFF30, 0xFF50 },
+ { 0xFF31, 0xFF51 },
+ { 0xFF32, 0xFF52 },
+ { 0xFF33, 0xFF53 },
+ { 0xFF34, 0xFF54 },
+ { 0xFF35, 0xFF55 },
+ { 0xFF36, 0xFF56 },
+ { 0xFF37, 0xFF57 },
+ { 0xFF38, 0xFF58 },
+ { 0xFF39, 0xFF59 },
+ { 0xFF3A, 0xFF5A },
+};
+
+static int _find_upper(int ch) {
+ int low = 0;
+ int high = CAPS_LEN - 1;
+ int middle;
+
+ while (low <= high) {
+ middle = (low + high) / 2;
+
+ if (ch < caps_table[middle][0]) {
+ high = middle - 1; //search low end of array
+ } else if (caps_table[middle][0] < ch) {
+ low = middle + 1; //search high end of array
+ } else {
+ return caps_table[middle][1];
+ }
+ }
+
+ return ch;
+}
+
+static int _find_lower(int ch) {
+ int low = 0;
+ int high = CAPS_LEN - 2;
+ int middle;
+
+ while (low <= high) {
+ middle = (low + high) / 2;
+
+ if (ch < reverse_caps_table[middle][0]) {
+ high = middle - 1; //search low end of array
+ } else if (reverse_caps_table[middle][0] < ch) {
+ low = middle + 1; //search high end of array
+ } else {
+ return reverse_caps_table[middle][1];
+ }
+ }
+
+ return ch;
+}
+
+#endif // UCAPS_H
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
new file mode 100644
index 0000000000..daeb7fbd17
--- /dev/null
+++ b/core/string/ustring.cpp
@@ -0,0 +1,4922 @@
+/*************************************************************************/
+/* ustring.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "ustring.h"
+
+#include "core/crypto/crypto_core.h"
+#include "core/math/color.h"
+#include "core/math/math_funcs.h"
+#include "core/os/memory.h"
+#include "core/string/print_string.h"
+#include "core/string/translation.h"
+#include "core/string/ucaps.h"
+#include "core/variant/variant.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <cstdint>
+
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_WARNINGS // to disable build-time warning which suggested to use strcpy_s instead strcpy
+#endif
+
+#if defined(MINGW_ENABLED) || defined(_MSC_VER)
+#define snprintf _snprintf_s
+#endif
+
+static const int MAX_DECIMALS = 32;
+
+static _FORCE_INLINE_ bool is_digit(char32_t c) {
+ return (c >= '0' && c <= '9');
+}
+
+static _FORCE_INLINE_ bool is_hex_digit(char32_t c) {
+ return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
+}
+
+static _FORCE_INLINE_ bool is_upper_case(char32_t c) {
+ return (c >= 'A' && c <= 'Z');
+}
+
+static _FORCE_INLINE_ bool is_lower_case(char32_t c) {
+ return (c >= 'a' && c <= 'z');
+}
+
+static _FORCE_INLINE_ char32_t lower_case(char32_t c) {
+ return (is_upper_case(c) ? (c + ('a' - 'A')) : c);
+}
+
+const char CharString::_null = 0;
+const char16_t Char16String::_null = 0;
+const char32_t String::_null = 0;
+
+bool is_symbol(char32_t c) {
+ return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' ');
+}
+
+bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end) {
+ const String &s = p_s;
+ int beg = CLAMP(p_col, 0, s.length());
+ int end = beg;
+
+ if (s[beg] > 32 || beg == s.length()) {
+ bool symbol = beg < s.length() && is_symbol(s[beg]);
+
+ while (beg > 0 && s[beg - 1] > 32 && (symbol == is_symbol(s[beg - 1]))) {
+ beg--;
+ }
+ while (end < s.length() && s[end + 1] > 32 && (symbol == is_symbol(s[end + 1]))) {
+ end++;
+ }
+
+ if (end < s.length()) {
+ end += 1;
+ }
+
+ r_beg = beg;
+ r_end = end;
+
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/*************************************************************************/
+/* Char16String */
+/*************************************************************************/
+
+bool Char16String::operator<(const Char16String &p_right) const {
+ if (length() == 0) {
+ return p_right.length() != 0;
+ }
+
+ return is_str_less(get_data(), p_right.get_data());
+}
+
+Char16String &Char16String::operator+=(char16_t p_char) {
+ resize(size() ? size() + 1 : 2);
+ set(length(), 0);
+ set(length() - 1, p_char);
+
+ return *this;
+}
+
+Char16String &Char16String::operator=(const char16_t *p_cstr) {
+ copy_from(p_cstr);
+ return *this;
+}
+
+const char16_t *Char16String::get_data() const {
+ if (size()) {
+ return &operator[](0);
+ } else {
+ return u"";
+ }
+}
+
+void Char16String::copy_from(const char16_t *p_cstr) {
+ if (!p_cstr) {
+ resize(0);
+ return;
+ }
+
+ const char16_t *s = p_cstr;
+ for (; *s; s++) {
+ }
+ size_t len = s - p_cstr;
+
+ if (len == 0) {
+ resize(0);
+ return;
+ }
+
+ Error err = resize(++len); // include terminating null char
+
+ ERR_FAIL_COND_MSG(err != OK, "Failed to copy char16_t string.");
+
+ memcpy(ptrw(), p_cstr, len * sizeof(char16_t));
+}
+
+/*************************************************************************/
+/* CharString */
+/*************************************************************************/
+
+bool CharString::operator<(const CharString &p_right) const {
+ if (length() == 0) {
+ return p_right.length() != 0;
+ }
+
+ return is_str_less(get_data(), p_right.get_data());
+}
+
+CharString &CharString::operator+=(char p_char) {
+ resize(size() ? size() + 1 : 2);
+ set(length(), 0);
+ set(length() - 1, p_char);
+
+ return *this;
+}
+
+CharString &CharString::operator=(const char *p_cstr) {
+ copy_from(p_cstr);
+ return *this;
+}
+
+const char *CharString::get_data() const {
+ if (size()) {
+ return &operator[](0);
+ } else {
+ return "";
+ }
+}
+
+void CharString::copy_from(const char *p_cstr) {
+ if (!p_cstr) {
+ resize(0);
+ return;
+ }
+
+ size_t len = strlen(p_cstr);
+
+ if (len == 0) {
+ resize(0);
+ return;
+ }
+
+ Error err = resize(++len); // include terminating null char
+
+ ERR_FAIL_COND_MSG(err != OK, "Failed to copy C-string.");
+
+ memcpy(ptrw(), p_cstr, len);
+}
+
+/*************************************************************************/
+/* String */
+/*************************************************************************/
+
+//kind of poor should be rewritten properly
+String String::word_wrap(int p_chars_per_line) const {
+ int from = 0;
+ int last_space = 0;
+ String ret;
+ for (int i = 0; i < length(); i++) {
+ if (i - from >= p_chars_per_line) {
+ if (last_space == -1) {
+ ret += substr(from, i - from + 1) + "\n";
+ } else {
+ ret += substr(from, last_space - from) + "\n";
+ i = last_space; //rewind
+ }
+ from = i + 1;
+ last_space = -1;
+ } else if (operator[](i) == ' ' || operator[](i) == '\t') {
+ last_space = i;
+ } else if (operator[](i) == '\n') {
+ ret += substr(from, i - from) + "\n";
+ from = i + 1;
+ last_space = -1;
+ }
+ }
+
+ if (from < length()) {
+ ret += substr(from, length());
+ }
+
+ return ret;
+}
+
+Error String::parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path) const {
+ // Splits the URL into scheme, host, port, path. Strip credentials when present.
+ String base = *this;
+ r_scheme = "";
+ r_host = "";
+ r_port = 0;
+ r_path = "";
+ int pos = base.find("://");
+ // Scheme
+ if (pos != -1) {
+ r_scheme = base.substr(0, pos + 3).to_lower();
+ base = base.substr(pos + 3, base.length() - pos - 3);
+ }
+ pos = base.find("/");
+ // Path
+ if (pos != -1) {
+ r_path = base.substr(pos, base.length() - pos);
+ base = base.substr(0, pos);
+ }
+ // Host
+ pos = base.find("@");
+ if (pos != -1) {
+ // Strip credentials
+ base = base.substr(pos + 1, base.length() - pos - 1);
+ }
+ if (base.begins_with("[")) {
+ // Literal IPv6
+ pos = base.rfind("]");
+ if (pos == -1) {
+ return ERR_INVALID_PARAMETER;
+ }
+ r_host = base.substr(1, pos - 1);
+ base = base.substr(pos + 1, base.length() - pos - 1);
+ } else {
+ // Anything else
+ if (base.get_slice_count(":") > 2) {
+ return ERR_INVALID_PARAMETER;
+ }
+ pos = base.rfind(":");
+ if (pos == -1) {
+ r_host = base;
+ base = "";
+ } else {
+ r_host = base.substr(0, pos);
+ base = base.substr(pos, base.length() - pos);
+ }
+ }
+ if (r_host.is_empty()) {
+ return ERR_INVALID_PARAMETER;
+ }
+ r_host = r_host.to_lower();
+ // Port
+ if (base.begins_with(":")) {
+ base = base.substr(1, base.length() - 1);
+ if (!base.is_valid_int()) {
+ return ERR_INVALID_PARAMETER;
+ }
+ r_port = base.to_int();
+ if (r_port < 1 || r_port > 65535) {
+ return ERR_INVALID_PARAMETER;
+ }
+ }
+ return OK;
+}
+
+void String::copy_from(const char *p_cstr) {
+ // copy Latin-1 encoded c-string directly
+ if (!p_cstr) {
+ resize(0);
+ return;
+ }
+
+ int len = 0;
+ const char *ptr = p_cstr;
+ while (*(ptr++) != 0) {
+ len++;
+ }
+
+ if (len == 0) {
+ resize(0);
+ return;
+ }
+
+ resize(len + 1); // include 0
+
+ char32_t *dst = this->ptrw();
+
+ for (int i = 0; i < len + 1; i++) {
+ dst[i] = p_cstr[i];
+ }
+}
+
+void String::copy_from(const char *p_cstr, const int p_clip_to) {
+ // copy Latin-1 encoded c-string directly
+ if (!p_cstr) {
+ resize(0);
+ return;
+ }
+
+ int len = 0;
+ const char *ptr = p_cstr;
+ while ((p_clip_to < 0 || len < p_clip_to) && *(ptr++) != 0) {
+ len++;
+ }
+
+ if (len == 0) {
+ resize(0);
+ return;
+ }
+
+ resize(len + 1); // include 0
+
+ char32_t *dst = this->ptrw();
+
+ for (int i = 0; i < len; i++) {
+ dst[i] = p_cstr[i];
+ }
+ dst[len] = 0;
+}
+
+void String::copy_from(const wchar_t *p_cstr) {
+#ifdef WINDOWS_ENABLED
+ // wchar_t is 16-bit, parse as UTF-16
+ parse_utf16((const char16_t *)p_cstr);
+#else
+ // wchar_t is 32-bit, copy directly
+ copy_from((const char32_t *)p_cstr);
+#endif
+}
+
+void String::copy_from(const wchar_t *p_cstr, const int p_clip_to) {
+#ifdef WINDOWS_ENABLED
+ // wchar_t is 16-bit, parse as UTF-16
+ parse_utf16((const char16_t *)p_cstr, p_clip_to);
+#else
+ // wchar_t is 32-bit, copy directly
+ copy_from((const char32_t *)p_cstr, p_clip_to);
+#endif
+}
+
+void String::copy_from(const char32_t &p_char) {
+ resize(2);
+ if ((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff)) {
+ print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(p_char, 16) + ".");
+ set(0, 0xfffd);
+ } else {
+ set(0, p_char);
+ }
+ set(1, 0);
+}
+
+void String::copy_from(const char32_t *p_cstr) {
+ if (!p_cstr) {
+ resize(0);
+ return;
+ }
+
+ int len = 0;
+ const char32_t *ptr = p_cstr;
+ while (*(ptr++) != 0) {
+ len++;
+ }
+
+ if (len == 0) {
+ resize(0);
+ return;
+ }
+
+ copy_from_unchecked(p_cstr, len);
+}
+
+void String::copy_from(const char32_t *p_cstr, const int p_clip_to) {
+ if (!p_cstr) {
+ resize(0);
+ return;
+ }
+
+ int len = 0;
+ const char32_t *ptr = p_cstr;
+ while ((p_clip_to < 0 || len < p_clip_to) && *(ptr++) != 0) {
+ len++;
+ }
+
+ if (len == 0) {
+ resize(0);
+ return;
+ }
+
+ copy_from_unchecked(p_cstr, len);
+}
+
+// assumes the following have already been validated:
+// p_char != nullptr
+// p_length > 0
+// p_length <= p_char strlen
+void String::copy_from_unchecked(const char32_t *p_char, const int p_length) {
+ resize(p_length + 1);
+ set(p_length, 0);
+
+ char32_t *dst = ptrw();
+
+ for (int i = 0; i < p_length; i++) {
+ if ((p_char[i] >= 0xd800 && p_char[i] <= 0xdfff) || (p_char[i] > 0x10ffff)) {
+ print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(p_char[i], 16) + ".");
+ dst[i] = 0xfffd;
+ } else {
+ dst[i] = p_char[i];
+ }
+ }
+}
+
+void String::operator=(const char *p_str) {
+ copy_from(p_str);
+}
+
+void String::operator=(const char32_t *p_str) {
+ copy_from(p_str);
+}
+
+void String::operator=(const wchar_t *p_str) {
+ copy_from(p_str);
+}
+
+String String::operator+(const String &p_str) const {
+ String res = *this;
+ res += p_str;
+ return res;
+}
+
+String operator+(const char *p_chr, const String &p_str) {
+ String tmp = p_chr;
+ tmp += p_str;
+ return tmp;
+}
+
+String operator+(const wchar_t *p_chr, const String &p_str) {
+#ifdef WINDOWS_ENABLED
+ // wchar_t is 16-bit
+ String tmp = String::utf16((const char16_t *)p_chr);
+#else
+ // wchar_t is 32-bi
+ String tmp = (const char32_t *)p_chr;
+#endif
+ tmp += p_str;
+ return tmp;
+}
+
+String operator+(char32_t p_chr, const String &p_str) {
+ return (String::chr(p_chr) + p_str);
+}
+
+String &String::operator+=(const String &p_str) {
+ if (is_empty()) {
+ *this = p_str;
+ return *this;
+ }
+
+ if (p_str.is_empty()) {
+ return *this;
+ }
+
+ int from = length();
+
+ resize(length() + p_str.size());
+
+ const char32_t *src = p_str.get_data();
+ char32_t *dst = ptrw();
+
+ set(length(), 0);
+
+ for (int i = 0; i < p_str.length(); i++) {
+ dst[from + i] = src[i];
+ }
+
+ return *this;
+}
+
+String &String::operator+=(const char *p_str) {
+ if (!p_str || p_str[0] == 0) {
+ return *this;
+ }
+
+ int src_len = 0;
+ const char *ptr = p_str;
+ while (*(ptr++) != 0) {
+ src_len++;
+ }
+
+ int from = length();
+
+ resize(from + src_len + 1);
+
+ char32_t *dst = ptrw();
+
+ set(length(), 0);
+
+ for (int i = 0; i < src_len; i++) {
+ dst[from + i] = p_str[i];
+ }
+
+ return *this;
+}
+
+String &String::operator+=(const wchar_t *p_str) {
+#ifdef WINDOWS_ENABLED
+ // wchar_t is 16-bit
+ *this += String::utf16((const char16_t *)p_str);
+#else
+ // wchar_t is 32-bit
+ *this += String((const char32_t *)p_str);
+#endif
+ return *this;
+}
+
+String &String::operator+=(const char32_t *p_str) {
+ *this += String(p_str);
+ return *this;
+}
+
+String &String::operator+=(char32_t p_char) {
+ resize(size() ? size() + 1 : 2);
+ set(length(), 0);
+ if ((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff)) {
+ print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(p_char, 16) + ".");
+ set(length() - 1, 0xfffd);
+ } else {
+ set(length() - 1, p_char);
+ }
+
+ return *this;
+}
+
+bool String::operator==(const char *p_str) const {
+ // compare Latin-1 encoded c-string
+ int len = 0;
+ const char *aux = p_str;
+
+ while (*(aux++) != 0) {
+ len++;
+ }
+
+ if (length() != len) {
+ return false;
+ }
+ if (is_empty()) {
+ return true;
+ }
+
+ int l = length();
+
+ const char32_t *dst = get_data();
+
+ // Compare char by char
+ for (int i = 0; i < l; i++) {
+ if ((char32_t)p_str[i] != dst[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool String::operator==(const wchar_t *p_str) const {
+#ifdef WINDOWS_ENABLED
+ // wchar_t is 16-bit, parse as UTF-16
+ return *this == String::utf16((const char16_t *)p_str);
+#else
+ // wchar_t is 32-bit, compare char by char
+ return *this == (const char32_t *)p_str;
+#endif
+}
+
+bool String::operator==(const char32_t *p_str) const {
+ int len = 0;
+ const char32_t *aux = p_str;
+
+ while (*(aux++) != 0) {
+ len++;
+ }
+
+ if (length() != len) {
+ return false;
+ }
+ if (is_empty()) {
+ return true;
+ }
+
+ int l = length();
+
+ const char32_t *dst = get_data();
+
+ /* Compare char by char */
+ for (int i = 0; i < l; i++) {
+ if (p_str[i] != dst[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool String::operator==(const String &p_str) const {
+ if (length() != p_str.length()) {
+ return false;
+ }
+ if (is_empty()) {
+ return true;
+ }
+
+ int l = length();
+
+ const char32_t *src = get_data();
+ const char32_t *dst = p_str.get_data();
+
+ /* Compare char by char */
+ for (int i = 0; i < l; i++) {
+ if (src[i] != dst[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool String::operator==(const StrRange &p_str_range) const {
+ int len = p_str_range.len;
+
+ if (length() != len) {
+ return false;
+ }
+ if (is_empty()) {
+ return true;
+ }
+
+ const char32_t *c_str = p_str_range.c_str;
+ const char32_t *dst = &operator[](0);
+
+ /* Compare char by char */
+ for (int i = 0; i < len; i++) {
+ if (c_str[i] != dst[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool operator==(const char *p_chr, const String &p_str) {
+ return p_str == p_chr;
+}
+
+bool operator==(const wchar_t *p_chr, const String &p_str) {
+#ifdef WINDOWS_ENABLED
+ // wchar_t is 16-bit
+ return p_str == String::utf16((const char16_t *)p_chr);
+#else
+ // wchar_t is 32-bi
+ return p_str == String((const char32_t *)p_chr);
+#endif
+}
+
+bool operator!=(const char *p_chr, const String &p_str) {
+ return !(p_str == p_chr);
+}
+
+bool operator!=(const wchar_t *p_chr, const String &p_str) {
+#ifdef WINDOWS_ENABLED
+ // wchar_t is 16-bit
+ return !(p_str == String::utf16((const char16_t *)p_chr));
+#else
+ // wchar_t is 32-bi
+ return !(p_str == String((const char32_t *)p_chr));
+#endif
+}
+
+bool String::operator!=(const char *p_str) const {
+ return (!(*this == p_str));
+}
+
+bool String::operator!=(const wchar_t *p_str) const {
+ return (!(*this == p_str));
+}
+
+bool String::operator!=(const char32_t *p_str) const {
+ return (!(*this == p_str));
+}
+
+bool String::operator!=(const String &p_str) const {
+ return !((*this == p_str));
+}
+
+bool String::operator<=(const String &p_str) const {
+ return !(p_str < *this);
+}
+
+bool String::operator>(const String &p_str) const {
+ return p_str < *this;
+}
+
+bool String::operator>=(const String &p_str) const {
+ return !(*this < p_str);
+}
+
+bool String::operator<(const char *p_str) const {
+ if (is_empty() && p_str[0] == 0) {
+ return false;
+ }
+ if (is_empty()) {
+ return true;
+ }
+ return is_str_less(get_data(), p_str);
+}
+
+bool String::operator<(const wchar_t *p_str) const {
+ if (is_empty() && p_str[0] == 0) {
+ return false;
+ }
+ if (is_empty()) {
+ return true;
+ }
+
+#ifdef WINDOWS_ENABLED
+ // wchar_t is 16-bit
+ return is_str_less(get_data(), String::utf16((const char16_t *)p_str).get_data());
+#else
+ // wchar_t is 32-bit
+ return is_str_less(get_data(), (const char32_t *)p_str);
+#endif
+}
+
+bool String::operator<(const char32_t *p_str) const {
+ if (is_empty() && p_str[0] == 0) {
+ return false;
+ }
+ if (is_empty()) {
+ return true;
+ }
+
+ return is_str_less(get_data(), p_str);
+}
+
+bool String::operator<(const String &p_str) const {
+ return operator<(p_str.get_data());
+}
+
+signed char String::nocasecmp_to(const String &p_str) const {
+ if (is_empty() && p_str.is_empty()) {
+ return 0;
+ }
+ if (is_empty()) {
+ return -1;
+ }
+ if (p_str.is_empty()) {
+ return 1;
+ }
+
+ const char32_t *that_str = p_str.get_data();
+ const char32_t *this_str = get_data();
+
+ while (true) {
+ if (*that_str == 0 && *this_str == 0) {
+ return 0; //we're equal
+ } else if (*this_str == 0) {
+ return -1; //if this is empty, and the other one is not, then we're less.. I think?
+ } else if (*that_str == 0) {
+ return 1; //otherwise the other one is smaller..
+ } else if (_find_upper(*this_str) < _find_upper(*that_str)) { //more than
+ return -1;
+ } else if (_find_upper(*this_str) > _find_upper(*that_str)) { //less than
+ return 1;
+ }
+
+ this_str++;
+ that_str++;
+ }
+}
+
+signed char String::casecmp_to(const String &p_str) const {
+ if (is_empty() && p_str.is_empty()) {
+ return 0;
+ }
+ if (is_empty()) {
+ return -1;
+ }
+ if (p_str.is_empty()) {
+ return 1;
+ }
+
+ const char32_t *that_str = p_str.get_data();
+ const char32_t *this_str = get_data();
+
+ while (true) {
+ if (*that_str == 0 && *this_str == 0) {
+ return 0; //we're equal
+ } else if (*this_str == 0) {
+ return -1; //if this is empty, and the other one is not, then we're less.. I think?
+ } else if (*that_str == 0) {
+ return 1; //otherwise the other one is smaller..
+ } else if (*this_str < *that_str) { //more than
+ return -1;
+ } else if (*this_str > *that_str) { //less than
+ return 1;
+ }
+
+ this_str++;
+ that_str++;
+ }
+}
+
+signed char String::naturalnocasecmp_to(const String &p_str) const {
+ const char32_t *this_str = get_data();
+ const char32_t *that_str = p_str.get_data();
+
+ if (this_str && that_str) {
+ while (*this_str == '.' || *that_str == '.') {
+ if (*this_str++ != '.') {
+ return 1;
+ }
+ if (*that_str++ != '.') {
+ return -1;
+ }
+ if (!*that_str) {
+ return 1;
+ }
+ if (!*this_str) {
+ return -1;
+ }
+ }
+
+ while (*this_str) {
+ if (!*that_str) {
+ return 1;
+ } else if (is_digit(*this_str)) {
+ if (!is_digit(*that_str)) {
+ return -1;
+ }
+
+ // Keep ptrs to start of numerical sequences
+ const char32_t *this_substr = this_str;
+ const char32_t *that_substr = that_str;
+
+ // Compare lengths of both numerical sequences, ignoring leading zeros
+ while (is_digit(*this_str)) {
+ this_str++;
+ }
+ while (is_digit(*that_str)) {
+ that_str++;
+ }
+ while (*this_substr == '0') {
+ this_substr++;
+ }
+ while (*that_substr == '0') {
+ that_substr++;
+ }
+ int this_len = this_str - this_substr;
+ int that_len = that_str - that_substr;
+
+ if (this_len < that_len) {
+ return -1;
+ } else if (this_len > that_len) {
+ return 1;
+ }
+
+ // If lengths equal, compare lexicographically
+ while (this_substr != this_str && that_substr != that_str) {
+ if (*this_substr < *that_substr) {
+ return -1;
+ } else if (*this_substr > *that_substr) {
+ return 1;
+ }
+ this_substr++;
+ that_substr++;
+ }
+ } else if (is_digit(*that_str)) {
+ return 1;
+ } else {
+ if (_find_upper(*this_str) < _find_upper(*that_str)) { //more than
+ return -1;
+ } else if (_find_upper(*this_str) > _find_upper(*that_str)) { //less than
+ return 1;
+ }
+
+ this_str++;
+ that_str++;
+ }
+ }
+ if (*that_str) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+const char32_t *String::get_data() const {
+ static const char32_t zero = 0;
+ return size() ? &operator[](0) : &zero;
+}
+
+void String::erase(int p_pos, int p_chars) {
+ *this = left(MAX(p_pos, 0)) + substr(p_pos + p_chars, length() - ((p_pos + p_chars)));
+}
+
+String String::capitalize() const {
+ String aux = this->camelcase_to_underscore(true).replace("_", " ").strip_edges();
+ String cap;
+ for (int i = 0; i < aux.get_slice_count(" "); i++) {
+ String slice = aux.get_slicec(' ', i);
+ if (slice.length() > 0) {
+ slice[0] = _find_upper(slice[0]);
+ if (i > 0) {
+ cap += " ";
+ }
+ cap += slice;
+ }
+ }
+
+ return cap;
+}
+
+String String::camelcase_to_underscore(bool lowercase) const {
+ const char32_t *cstr = get_data();
+ String new_string;
+ int start_index = 0;
+
+ for (int i = 1; i < this->size(); i++) {
+ bool is_upper = is_upper_case(cstr[i]);
+ bool is_number = is_digit(cstr[i]);
+
+ bool are_next_2_lower = false;
+ bool is_next_lower = false;
+ bool is_next_number = false;
+ bool was_precedent_upper = is_upper_case(cstr[i - 1]);
+ bool was_precedent_number = is_digit(cstr[i - 1]);
+
+ if (i + 2 < this->size()) {
+ are_next_2_lower = is_lower_case(cstr[i + 1]) && is_lower_case(cstr[i + 2]);
+ }
+
+ if (i + 1 < this->size()) {
+ is_next_lower = is_lower_case(cstr[i + 1]);
+ is_next_number = is_digit(cstr[i + 1]);
+ }
+
+ const bool cond_a = is_upper && !was_precedent_upper && !was_precedent_number;
+ const bool cond_b = was_precedent_upper && is_upper && are_next_2_lower;
+ const bool cond_c = is_number && !was_precedent_number;
+ const bool can_break_number_letter = is_number && !was_precedent_number && is_next_lower;
+ const bool can_break_letter_number = !is_number && was_precedent_number && (is_next_lower || is_next_number);
+
+ bool should_split = cond_a || cond_b || cond_c || can_break_number_letter || can_break_letter_number;
+ if (should_split) {
+ new_string += this->substr(start_index, i - start_index) + "_";
+ start_index = i;
+ }
+ }
+
+ new_string += this->substr(start_index, this->size() - start_index);
+ return lowercase ? new_string.to_lower() : new_string;
+}
+
+String String::get_with_code_lines() const {
+ const Vector<String> lines = split("\n");
+ String ret;
+ for (int i = 0; i < lines.size(); i++) {
+ if (i > 0) {
+ ret += "\n";
+ }
+ ret += vformat("%4d | %s", i + 1, lines[i]);
+ }
+ return ret;
+}
+
+int String::get_slice_count(String p_splitter) const {
+ if (is_empty()) {
+ return 0;
+ }
+ if (p_splitter.is_empty()) {
+ return 0;
+ }
+
+ int pos = 0;
+ int slices = 1;
+
+ while ((pos = find(p_splitter, pos)) >= 0) {
+ slices++;
+ pos += p_splitter.length();
+ }
+
+ return slices;
+}
+
+String String::get_slice(String p_splitter, int p_slice) const {
+ if (is_empty() || p_splitter.is_empty()) {
+ return "";
+ }
+
+ int pos = 0;
+ int prev_pos = 0;
+ //int slices=1;
+ if (p_slice < 0) {
+ return "";
+ }
+ if (find(p_splitter) == -1) {
+ return *this;
+ }
+
+ int i = 0;
+ while (true) {
+ pos = find(p_splitter, pos);
+ if (pos == -1) {
+ pos = length(); //reached end
+ }
+
+ int from = prev_pos;
+ //int to=pos;
+
+ if (p_slice == i) {
+ return substr(from, pos - from);
+ }
+
+ if (pos == length()) { //reached end and no find
+ break;
+ }
+ pos += p_splitter.length();
+ prev_pos = pos;
+ i++;
+ }
+
+ return ""; //no find!
+}
+
+String String::get_slicec(char32_t p_splitter, int p_slice) const {
+ if (is_empty()) {
+ return String();
+ }
+
+ if (p_slice < 0) {
+ return String();
+ }
+
+ const char32_t *c = this->ptr();
+ int i = 0;
+ int prev = 0;
+ int count = 0;
+ while (true) {
+ if (c[i] == 0 || c[i] == p_splitter) {
+ if (p_slice == count) {
+ return substr(prev, i - prev);
+ } else if (c[i] == 0) {
+ return String();
+ } else {
+ count++;
+ prev = i + 1;
+ }
+ }
+
+ i++;
+ }
+}
+
+Vector<String> String::split_spaces() const {
+ Vector<String> ret;
+ int from = 0;
+ int i = 0;
+ int len = length();
+ if (len == 0) {
+ return ret;
+ }
+
+ bool inside = false;
+
+ while (true) {
+ bool empty = operator[](i) < 33;
+
+ if (i == 0) {
+ inside = !empty;
+ }
+
+ if (!empty && !inside) {
+ inside = true;
+ from = i;
+ }
+
+ if (empty && inside) {
+ ret.push_back(substr(from, i - from));
+ inside = false;
+ }
+
+ if (i == len) {
+ break;
+ }
+ i++;
+ }
+
+ return ret;
+}
+
+Vector<String> String::split(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const {
+ Vector<String> ret;
+ int from = 0;
+ int len = length();
+
+ while (true) {
+ int end = find(p_splitter, from);
+ if (end < 0) {
+ end = len;
+ }
+ if (p_allow_empty || (end > from)) {
+ if (p_maxsplit <= 0) {
+ ret.push_back(substr(from, end - from));
+ } else {
+ // Put rest of the string and leave cycle.
+ if (p_maxsplit == ret.size()) {
+ ret.push_back(substr(from, len));
+ break;
+ }
+
+ // Otherwise, push items until positive limit is reached.
+ ret.push_back(substr(from, end - from));
+ }
+ }
+
+ if (end == len) {
+ break;
+ }
+
+ from = end + p_splitter.length();
+ }
+
+ return ret;
+}
+
+Vector<String> String::rsplit(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const {
+ Vector<String> ret;
+ const int len = length();
+ int remaining_len = len;
+
+ while (true) {
+ if (remaining_len < p_splitter.length() || (p_maxsplit > 0 && p_maxsplit == ret.size())) {
+ // no room for another splitter or hit max splits, push what's left and we're done
+ if (p_allow_empty || remaining_len > 0) {
+ ret.push_back(substr(0, remaining_len));
+ }
+ break;
+ }
+
+ int left_edge = rfind(p_splitter, remaining_len - p_splitter.length());
+
+ if (left_edge < 0) {
+ // no more splitters, we're done
+ ret.push_back(substr(0, remaining_len));
+ break;
+ }
+
+ int substr_start = left_edge + p_splitter.length();
+ if (p_allow_empty || substr_start < remaining_len) {
+ ret.push_back(substr(substr_start, remaining_len - substr_start));
+ }
+
+ remaining_len = left_edge;
+ }
+
+ ret.reverse();
+ return ret;
+}
+
+Vector<float> String::split_floats(const String &p_splitter, bool p_allow_empty) const {
+ Vector<float> ret;
+ int from = 0;
+ int len = length();
+
+ while (true) {
+ int end = find(p_splitter, from);
+ if (end < 0) {
+ end = len;
+ }
+ if (p_allow_empty || (end > from)) {
+ ret.push_back(String::to_float(&get_data()[from]));
+ }
+
+ if (end == len) {
+ break;
+ }
+
+ from = end + p_splitter.length();
+ }
+
+ return ret;
+}
+
+Vector<float> String::split_floats_mk(const Vector<String> &p_splitters, bool p_allow_empty) const {
+ Vector<float> ret;
+ int from = 0;
+ int len = length();
+
+ while (true) {
+ int idx;
+ int end = findmk(p_splitters, from, &idx);
+ int spl_len = 1;
+ if (end < 0) {
+ end = len;
+ } else {
+ spl_len = p_splitters[idx].length();
+ }
+
+ if (p_allow_empty || (end > from)) {
+ ret.push_back(String::to_float(&get_data()[from]));
+ }
+
+ if (end == len) {
+ break;
+ }
+
+ from = end + spl_len;
+ }
+
+ return ret;
+}
+
+Vector<int> String::split_ints(const String &p_splitter, bool p_allow_empty) const {
+ Vector<int> ret;
+ int from = 0;
+ int len = length();
+
+ while (true) {
+ int end = find(p_splitter, from);
+ if (end < 0) {
+ end = len;
+ }
+ if (p_allow_empty || (end > from)) {
+ ret.push_back(String::to_int(&get_data()[from], end - from));
+ }
+
+ if (end == len) {
+ break;
+ }
+
+ from = end + p_splitter.length();
+ }
+
+ return ret;
+}
+
+Vector<int> String::split_ints_mk(const Vector<String> &p_splitters, bool p_allow_empty) const {
+ Vector<int> ret;
+ int from = 0;
+ int len = length();
+
+ while (true) {
+ int idx;
+ int end = findmk(p_splitters, from, &idx);
+ int spl_len = 1;
+ if (end < 0) {
+ end = len;
+ } else {
+ spl_len = p_splitters[idx].length();
+ }
+
+ if (p_allow_empty || (end > from)) {
+ ret.push_back(String::to_int(&get_data()[from], end - from));
+ }
+
+ if (end == len) {
+ break;
+ }
+
+ from = end + spl_len;
+ }
+
+ return ret;
+}
+
+String String::join(Vector<String> parts) const {
+ String ret;
+ for (int i = 0; i < parts.size(); ++i) {
+ if (i > 0) {
+ ret += *this;
+ }
+ ret += parts[i];
+ }
+ return ret;
+}
+
+char32_t String::char_uppercase(char32_t p_char) {
+ return _find_upper(p_char);
+}
+
+char32_t String::char_lowercase(char32_t p_char) {
+ return _find_lower(p_char);
+}
+
+String String::to_upper() const {
+ String upper = *this;
+
+ for (int i = 0; i < upper.size(); i++) {
+ const char32_t s = upper[i];
+ const char32_t t = _find_upper(s);
+ if (s != t) { // avoid copy on write
+ upper[i] = t;
+ }
+ }
+
+ return upper;
+}
+
+String String::to_lower() const {
+ String lower = *this;
+
+ for (int i = 0; i < lower.size(); i++) {
+ const char32_t s = lower[i];
+ const char32_t t = _find_lower(s);
+ if (s != t) { // avoid copy on write
+ lower[i] = t;
+ }
+ }
+
+ return lower;
+}
+
+String String::chr(char32_t p_char) {
+ char32_t c[2] = { p_char, 0 };
+ return String(c);
+}
+
+String String::num(double p_num, int p_decimals) {
+ if (Math::is_nan(p_num)) {
+ return "nan";
+ }
+
+ if (Math::is_inf(p_num)) {
+ if (signbit(p_num)) {
+ return "-inf";
+ } else {
+ return "inf";
+ }
+ }
+
+ if (p_decimals < 0) {
+ p_decimals = 14;
+ const double abs_num = ABS(p_num);
+ if (abs_num > 10) {
+ // We want to align the digits to the above sane default, so we only
+ // need to subtract log10 for numbers with a positive power of ten.
+ p_decimals -= (int)floor(log10(abs_num));
+ }
+ }
+ if (p_decimals > MAX_DECIMALS) {
+ p_decimals = MAX_DECIMALS;
+ }
+
+ char fmt[7];
+ fmt[0] = '%';
+ fmt[1] = '.';
+
+ if (p_decimals < 0) {
+ fmt[1] = 'l';
+ fmt[2] = 'f';
+ fmt[3] = 0;
+ } else if (p_decimals < 10) {
+ fmt[2] = '0' + p_decimals;
+ fmt[3] = 'l';
+ fmt[4] = 'f';
+ fmt[5] = 0;
+ } else {
+ fmt[2] = '0' + (p_decimals / 10);
+ fmt[3] = '0' + (p_decimals % 10);
+ fmt[4] = 'l';
+ fmt[5] = 'f';
+ fmt[6] = 0;
+ }
+ char buf[256];
+
+#if defined(__GNUC__) || defined(_MSC_VER)
+ snprintf(buf, 256, fmt, p_num);
+#else
+ sprintf(buf, fmt, p_num);
+#endif
+
+ buf[255] = 0;
+ //destroy trailing zeroes
+ {
+ bool period = false;
+ int z = 0;
+ while (buf[z]) {
+ if (buf[z] == '.') {
+ period = true;
+ }
+ z++;
+ }
+
+ if (period) {
+ z--;
+ while (z > 0) {
+ if (buf[z] == '0') {
+ buf[z] = 0;
+ } else if (buf[z] == '.') {
+ buf[z] = 0;
+ break;
+ } else {
+ break;
+ }
+
+ z--;
+ }
+ }
+ }
+
+ return buf;
+}
+
+String String::num_int64(int64_t p_num, int base, bool capitalize_hex) {
+ bool sign = p_num < 0;
+
+ int64_t n = p_num;
+
+ int chars = 0;
+ do {
+ n /= base;
+ chars++;
+ } while (n);
+
+ if (sign) {
+ chars++;
+ }
+ String s;
+ s.resize(chars + 1);
+ char32_t *c = s.ptrw();
+ c[chars] = 0;
+ n = p_num;
+ do {
+ int mod = ABS(n % base);
+ if (mod >= 10) {
+ char a = (capitalize_hex ? 'A' : 'a');
+ c[--chars] = a + (mod - 10);
+ } else {
+ c[--chars] = '0' + mod;
+ }
+
+ n /= base;
+ } while (n);
+
+ if (sign) {
+ c[0] = '-';
+ }
+
+ return s;
+}
+
+String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) {
+ uint64_t n = p_num;
+
+ int chars = 0;
+ do {
+ n /= base;
+ chars++;
+ } while (n);
+
+ String s;
+ s.resize(chars + 1);
+ char32_t *c = s.ptrw();
+ c[chars] = 0;
+ n = p_num;
+ do {
+ int mod = n % base;
+ if (mod >= 10) {
+ char a = (capitalize_hex ? 'A' : 'a');
+ c[--chars] = a + (mod - 10);
+ } else {
+ c[--chars] = '0' + mod;
+ }
+
+ n /= base;
+ } while (n);
+
+ return s;
+}
+
+String String::num_real(double p_num, bool p_trailing) {
+ if (Math::is_nan(p_num)) {
+ return "nan";
+ }
+
+ if (Math::is_inf(p_num)) {
+ if (signbit(p_num)) {
+ return "-inf";
+ } else {
+ return "inf";
+ }
+ }
+
+ String s;
+ String sd;
+
+ // Integer part.
+
+ bool neg = p_num < 0;
+ p_num = ABS(p_num);
+ int64_t intn = (int64_t)p_num;
+
+ // Decimal part.
+
+ if (intn != p_num) {
+ double dec = p_num - (double)intn;
+
+ int digit = 0;
+
+#ifdef REAL_T_IS_DOUBLE
+ int decimals = 14;
+ double tolerance = 1e-14;
+#else
+ int decimals = 6;
+ double tolerance = 1e-6;
+#endif
+ // We want to align the digits to the above sane default, so we only
+ // need to subtract log10 for numbers with a positive power of ten.
+ if (p_num > 10) {
+ decimals -= (int)floor(log10(p_num));
+ }
+
+ if (decimals > MAX_DECIMALS) {
+ decimals = MAX_DECIMALS;
+ }
+
+ // In case the value ends up ending in "99999", we want to add a
+ // tiny bit to the value we're checking when deciding when to stop,
+ // so we multiply by slightly above 1 (1 + 1e-7 or 1e-15).
+ double check_multiplier = 1 + tolerance / 10;
+
+ int64_t dec_int = 0;
+ int64_t dec_max = 0;
+
+ while (true) {
+ dec *= 10.0;
+ dec_int = dec_int * 10 + (int64_t)dec % 10;
+ dec_max = dec_max * 10 + 9;
+ digit++;
+
+ if ((dec - (double)(int64_t)(dec * check_multiplier)) < tolerance) {
+ break;
+ }
+
+ if (digit == decimals) {
+ break;
+ }
+ }
+
+ dec *= 10;
+ int last = (int64_t)dec % 10;
+
+ if (last > 5) {
+ if (dec_int == dec_max) {
+ dec_int = 0;
+ intn++;
+ } else {
+ dec_int++;
+ }
+ }
+
+ String decimal;
+ for (int i = 0; i < digit; i++) {
+ char num[2] = { 0, 0 };
+ num[0] = '0' + dec_int % 10;
+ decimal = num + decimal;
+ dec_int /= 10;
+ }
+ sd = '.' + decimal;
+ } else if (p_trailing) {
+ sd = ".0";
+ } else {
+ sd = "";
+ }
+
+ if (intn == 0) {
+ s = "0";
+ } else {
+ while (intn) {
+ char32_t num = '0' + (intn % 10);
+ intn /= 10;
+ s = num + s;
+ }
+ }
+
+ s = s + sd;
+ if (neg) {
+ s = "-" + s;
+ }
+ return s;
+}
+
+String String::num_scientific(double p_num) {
+ if (Math::is_nan(p_num)) {
+ return "nan";
+ }
+
+ if (Math::is_inf(p_num)) {
+ if (signbit(p_num)) {
+ return "-inf";
+ } else {
+ return "inf";
+ }
+ }
+
+ char buf[256];
+
+#if defined(__GNUC__) || defined(_MSC_VER)
+
+#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT)
+ // MinGW requires _set_output_format() to conform to C99 output for printf
+ unsigned int old_exponent_format = _set_output_format(_TWO_DIGIT_EXPONENT);
+#endif
+ snprintf(buf, 256, "%lg", p_num);
+
+#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT)
+ _set_output_format(old_exponent_format);
+#endif
+
+#else
+ sprintf(buf, "%.16lg", p_num);
+#endif
+
+ buf[255] = 0;
+
+ return buf;
+}
+
+String String::md5(const uint8_t *p_md5) {
+ return String::hex_encode_buffer(p_md5, 16);
+}
+
+String String::hex_encode_buffer(const uint8_t *p_buffer, int p_len) {
+ static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ String ret;
+ char v[2] = { 0, 0 };
+
+ for (int i = 0; i < p_len; i++) {
+ v[0] = hex[p_buffer[i] >> 4];
+ ret += v;
+ v[0] = hex[p_buffer[i] & 0xF];
+ ret += v;
+ }
+
+ return ret;
+}
+
+CharString String::ascii(bool p_allow_extended) const {
+ if (!length()) {
+ return CharString();
+ }
+
+ CharString cs;
+ cs.resize(size());
+
+ for (int i = 0; i < size(); i++) {
+ char32_t c = operator[](i);
+ if ((c <= 0x7f) || (c <= 0xff && p_allow_extended)) {
+ cs[i] = c;
+ } else {
+ print_error("Unicode parsing error: Cannot represent " + num_int64(c, 16) + " as ASCII/Latin-1 character.");
+ cs[i] = 0x20;
+ }
+ }
+
+ return cs;
+}
+
+String String::utf8(const char *p_utf8, int p_len) {
+ String ret;
+ ret.parse_utf8(p_utf8, p_len);
+
+ return ret;
+}
+
+bool String::parse_utf8(const char *p_utf8, int p_len) {
+#define _UNICERROR(m_err) print_error("Unicode parsing error: " + String(m_err) + ". Is the string valid UTF-8?");
+
+ if (!p_utf8) {
+ return true;
+ }
+
+ String aux;
+
+ int cstr_size = 0;
+ int str_size = 0;
+
+ /* HANDLE BOM (Byte Order Mark) */
+ if (p_len < 0 || p_len >= 3) {
+ bool has_bom = uint8_t(p_utf8[0]) == 0xef && uint8_t(p_utf8[1]) == 0xbb && uint8_t(p_utf8[2]) == 0xbf;
+ if (has_bom) {
+ //8-bit encoding, byte order has no meaning in UTF-8, just skip it
+ if (p_len >= 0) {
+ p_len -= 3;
+ }
+ p_utf8 += 3;
+ }
+ }
+
+ {
+ const char *ptrtmp = p_utf8;
+ const char *ptrtmp_limit = &p_utf8[p_len];
+ int skip = 0;
+ while (ptrtmp != ptrtmp_limit && *ptrtmp) {
+ if (skip == 0) {
+ uint8_t c = *ptrtmp >= 0 ? *ptrtmp : uint8_t(256 + *ptrtmp);
+
+ /* Determine the number of characters in sequence */
+ if ((c & 0x80) == 0) {
+ skip = 0;
+ } else if ((c & 0xe0) == 0xc0) {
+ skip = 1;
+ } else if ((c & 0xf0) == 0xe0) {
+ skip = 2;
+ } else if ((c & 0xf8) == 0xf0) {
+ skip = 3;
+ } else {
+ _UNICERROR("invalid skip at " + num_int64(cstr_size));
+ return true; //invalid utf8
+ }
+
+ if (skip == 1 && (c & 0x1e) == 0) {
+ _UNICERROR("overlong rejected at " + num_int64(cstr_size));
+ return true; //reject overlong
+ }
+
+ str_size++;
+
+ } else {
+ --skip;
+ }
+
+ cstr_size++;
+ ptrtmp++;
+ }
+
+ if (skip) {
+ _UNICERROR("no space left");
+ return true; //not enough space
+ }
+ }
+
+ if (str_size == 0) {
+ clear();
+ return false;
+ }
+
+ resize(str_size + 1);
+ char32_t *dst = ptrw();
+ dst[str_size] = 0;
+
+ while (cstr_size) {
+ int len = 0;
+
+ /* Determine the number of characters in sequence */
+ if ((*p_utf8 & 0x80) == 0) {
+ len = 1;
+ } else if ((*p_utf8 & 0xe0) == 0xc0) {
+ len = 2;
+ } else if ((*p_utf8 & 0xf0) == 0xe0) {
+ len = 3;
+ } else if ((*p_utf8 & 0xf8) == 0xf0) {
+ len = 4;
+ } else {
+ _UNICERROR("invalid len");
+ return true; //invalid UTF8
+ }
+
+ if (len > cstr_size) {
+ _UNICERROR("no space left");
+ return true; //not enough space
+ }
+
+ if (len == 2 && (*p_utf8 & 0x1E) == 0) {
+ _UNICERROR("no space left");
+ return true; //reject overlong
+ }
+
+ /* Convert the first character */
+
+ uint32_t unichar = 0;
+
+ if (len == 1) {
+ unichar = *p_utf8;
+ } else {
+ unichar = (0xff >> (len + 1)) & *p_utf8;
+
+ for (int i = 1; i < len; i++) {
+ if ((p_utf8[i] & 0xc0) != 0x80) {
+ _UNICERROR("invalid utf8");
+ return true; //invalid utf8
+ }
+ if (unichar == 0 && i == 2 && ((p_utf8[i] & 0x7f) >> (7 - len)) == 0) {
+ _UNICERROR("invalid utf8 overlong");
+ return true; //no overlong
+ }
+ unichar = (unichar << 6) | (p_utf8[i] & 0x3f);
+ }
+ }
+ if (unichar >= 0xd800 && unichar <= 0xdfff) {
+ _UNICERROR("invalid code point");
+ return CharString();
+ }
+
+ *(dst++) = unichar;
+ cstr_size -= len;
+ p_utf8 += len;
+ }
+
+ return false;
+#undef _UNICERROR
+}
+
+CharString String::utf8() const {
+ int l = length();
+ if (!l) {
+ return CharString();
+ }
+
+ const char32_t *d = &operator[](0);
+ int fl = 0;
+ for (int i = 0; i < l; i++) {
+ uint32_t c = d[i];
+ if (c <= 0x7f) { // 7 bits.
+ fl += 1;
+ } else if (c <= 0x7ff) { // 11 bits
+ fl += 2;
+ } else if (c <= 0xffff) { // 16 bits
+ fl += 3;
+ } else if (c <= 0x0010ffff) { // 21 bits
+ fl += 4;
+ } else {
+ print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(c, 16) + ".");
+ return CharString();
+ }
+ if (c >= 0xd800 && c <= 0xdfff) {
+ print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(c, 16) + ".");
+ return CharString();
+ }
+ }
+
+ CharString utf8s;
+ if (fl == 0) {
+ return utf8s;
+ }
+
+ utf8s.resize(fl + 1);
+ uint8_t *cdst = (uint8_t *)utf8s.get_data();
+
+#define APPEND_CHAR(m_c) *(cdst++) = m_c
+
+ for (int i = 0; i < l; i++) {
+ uint32_t c = d[i];
+
+ if (c <= 0x7f) { // 7 bits.
+ APPEND_CHAR(c);
+ } else if (c <= 0x7ff) { // 11 bits
+ APPEND_CHAR(uint32_t(0xc0 | ((c >> 6) & 0x1f))); // Top 5 bits.
+ APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits.
+ } else if (c <= 0xffff) { // 16 bits
+ APPEND_CHAR(uint32_t(0xe0 | ((c >> 12) & 0x0f))); // Top 4 bits.
+ APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Middle 6 bits.
+ APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits.
+ } else { // 21 bits
+ APPEND_CHAR(uint32_t(0xf0 | ((c >> 18) & 0x07))); // Top 3 bits.
+ APPEND_CHAR(uint32_t(0x80 | ((c >> 12) & 0x3f))); // Upper middle 6 bits.
+ APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Lower middle 6 bits.
+ APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits.
+ }
+ }
+#undef APPEND_CHAR
+ *cdst = 0; //trailing zero
+
+ return utf8s;
+}
+
+String String::utf16(const char16_t *p_utf16, int p_len) {
+ String ret;
+ ret.parse_utf16(p_utf16, p_len);
+
+ return ret;
+}
+
+bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
+#define _UNICERROR(m_err) print_error("Unicode parsing error: " + String(m_err) + ". Is the string valid UTF-16?");
+
+ if (!p_utf16) {
+ return true;
+ }
+
+ String aux;
+
+ int cstr_size = 0;
+ int str_size = 0;
+
+ /* HANDLE BOM (Byte Order Mark) */
+ bool byteswap = false; // assume correct endianness if no BOM found
+ if (p_len < 0 || p_len >= 1) {
+ bool has_bom = false;
+ if (uint16_t(p_utf16[0]) == 0xfeff) { // correct BOM, read as is
+ has_bom = true;
+ byteswap = false;
+ } else if (uint16_t(p_utf16[0]) == 0xfffe) { // backwards BOM, swap bytes
+ has_bom = true;
+ byteswap = true;
+ }
+ if (has_bom) {
+ if (p_len >= 0) {
+ p_len -= 1;
+ }
+ p_utf16 += 1;
+ }
+ }
+
+ {
+ const char16_t *ptrtmp = p_utf16;
+ const char16_t *ptrtmp_limit = &p_utf16[p_len];
+ int skip = 0;
+ while (ptrtmp != ptrtmp_limit && *ptrtmp) {
+ uint32_t c = (byteswap) ? BSWAP16(*ptrtmp) : *ptrtmp;
+ if (skip == 0) {
+ if ((c & 0xfffffc00) == 0xd800) {
+ skip = 1; // lead surrogate
+ } else if ((c & 0xfffffc00) == 0xdc00) {
+ _UNICERROR("invalid utf16 surrogate at " + num_int64(cstr_size));
+ return true; // invalid UTF16
+ } else {
+ skip = 0;
+ }
+ str_size++;
+ } else {
+ if ((c & 0xfffffc00) == 0xdc00) { // trail surrogate
+ --skip;
+ } else {
+ _UNICERROR("invalid utf16 surrogate at " + num_int64(cstr_size));
+ return true; // invalid UTF16
+ }
+ }
+
+ cstr_size++;
+ ptrtmp++;
+ }
+
+ if (skip) {
+ _UNICERROR("no space left");
+ return true; // not enough space
+ }
+ }
+
+ if (str_size == 0) {
+ clear();
+ return false;
+ }
+
+ resize(str_size + 1);
+ char32_t *dst = ptrw();
+ dst[str_size] = 0;
+
+ while (cstr_size) {
+ int len = 0;
+ uint32_t c = (byteswap) ? BSWAP16(*p_utf16) : *p_utf16;
+
+ if ((c & 0xfffffc00) == 0xd800) {
+ len = 2;
+ } else {
+ len = 1;
+ }
+
+ if (len > cstr_size) {
+ _UNICERROR("no space left");
+ return true; //not enough space
+ }
+
+ uint32_t unichar = 0;
+ if (len == 1) {
+ unichar = c;
+ } else {
+ uint32_t c2 = (byteswap) ? BSWAP16(p_utf16[1]) : p_utf16[1];
+ unichar = (c << 10UL) + c2 - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
+ }
+
+ *(dst++) = unichar;
+ cstr_size -= len;
+ p_utf16 += len;
+ }
+
+ return false;
+#undef _UNICERROR
+}
+
+Char16String String::utf16() const {
+ int l = length();
+ if (!l) {
+ return Char16String();
+ }
+
+ const char32_t *d = &operator[](0);
+ int fl = 0;
+ for (int i = 0; i < l; i++) {
+ uint32_t c = d[i];
+ if (c <= 0xffff) { // 16 bits.
+ fl += 1;
+ } else if (c <= 0x10ffff) { // 32 bits.
+ fl += 2;
+ } else {
+ print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(c, 16) + ".");
+ return Char16String();
+ }
+ if (c >= 0xd800 && c <= 0xdfff) {
+ print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(c, 16) + ".");
+ return Char16String();
+ }
+ }
+
+ Char16String utf16s;
+ if (fl == 0) {
+ return utf16s;
+ }
+
+ utf16s.resize(fl + 1);
+ uint16_t *cdst = (uint16_t *)utf16s.get_data();
+
+#define APPEND_CHAR(m_c) *(cdst++) = m_c
+
+ for (int i = 0; i < l; i++) {
+ uint32_t c = d[i];
+
+ if (c <= 0xffff) { // 16 bits.
+ APPEND_CHAR(c);
+ } else { // 32 bits.
+ APPEND_CHAR(uint32_t((c >> 10) + 0xd7c0)); // lead surrogate.
+ APPEND_CHAR(uint32_t((c & 0x3ff) | 0xdc00)); // trail surrogate.
+ }
+ }
+#undef APPEND_CHAR
+ *cdst = 0; //trailing zero
+
+ return utf16s;
+}
+
+String::String(const char *p_str) {
+ copy_from(p_str);
+}
+
+String::String(const wchar_t *p_str) {
+ copy_from(p_str);
+}
+
+String::String(const char32_t *p_str) {
+ copy_from(p_str);
+}
+
+String::String(const char *p_str, int p_clip_to_len) {
+ copy_from(p_str, p_clip_to_len);
+}
+
+String::String(const wchar_t *p_str, int p_clip_to_len) {
+ copy_from(p_str, p_clip_to_len);
+}
+
+String::String(const char32_t *p_str, int p_clip_to_len) {
+ copy_from(p_str, p_clip_to_len);
+}
+
+String::String(const StrRange &p_range) {
+ if (!p_range.c_str) {
+ return;
+ }
+ copy_from(p_range.c_str, p_range.len);
+}
+
+int64_t String::hex_to_int() const {
+ int len = length();
+ if (len == 0) {
+ return 0;
+ }
+
+ const char32_t *s = ptr();
+
+ int64_t sign = s[0] == '-' ? -1 : 1;
+
+ if (sign < 0) {
+ s++;
+ }
+
+ if (len > 2 && s[0] == '0' && s[1] == 'x') {
+ s += 2;
+ }
+
+ int64_t hex = 0;
+
+ while (*s) {
+ char32_t c = lower_case(*s);
+ int64_t n;
+ if (is_digit(c)) {
+ n = c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ n = (c - 'a') + 10;
+ } else {
+ return 0;
+ }
+ // Check for overflow/underflow, with special case to ensure INT64_MIN does not result in error
+ bool overflow = ((hex > INT64_MAX / 16) && (sign == 1 || (sign == -1 && hex != (INT64_MAX >> 4) + 1))) || (sign == -1 && hex == (INT64_MAX >> 4) + 1 && c > '0');
+ ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as 64-bit integer, provided value is " + (sign == 1 ? "too big." : "too small."));
+ hex *= 16;
+ hex += n;
+ s++;
+ }
+
+ return hex * sign;
+}
+
+int64_t String::bin_to_int() const {
+ int len = length();
+ if (len == 0) {
+ return 0;
+ }
+
+ const char32_t *s = ptr();
+
+ int64_t sign = s[0] == '-' ? -1 : 1;
+
+ if (sign < 0) {
+ s++;
+ }
+
+ if (len > 2 && s[0] == '0' && s[1] == 'b') {
+ s += 2;
+ }
+
+ int64_t binary = 0;
+
+ while (*s) {
+ char32_t c = lower_case(*s);
+ int64_t n;
+ if (c == '0' || c == '1') {
+ n = c - '0';
+ } else {
+ return 0;
+ }
+ // Check for overflow/underflow, with special case to ensure INT64_MIN does not result in error
+ bool overflow = ((binary > INT64_MAX / 2) && (sign == 1 || (sign == -1 && binary != (INT64_MAX >> 1) + 1))) || (sign == -1 && binary == (INT64_MAX >> 1) + 1 && c > '0');
+ ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as 64-bit integer, provided value is " + (sign == 1 ? "too big." : "too small."));
+ binary *= 2;
+ binary += n;
+ s++;
+ }
+
+ return binary * sign;
+}
+
+int64_t String::to_int() const {
+ if (length() == 0) {
+ return 0;
+ }
+
+ int to = (find(".") >= 0) ? find(".") : length();
+
+ int64_t integer = 0;
+ int64_t sign = 1;
+
+ for (int i = 0; i < to; i++) {
+ char32_t c = operator[](i);
+ if (is_digit(c)) {
+ bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8')));
+ ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as 64-bit integer, provided value is " + (sign == 1 ? "too big." : "too small."));
+ integer *= 10;
+ integer += c - '0';
+
+ } else if (integer == 0 && c == '-') {
+ sign = -sign;
+ }
+ }
+
+ return integer * sign;
+}
+
+int64_t String::to_int(const char *p_str, int p_len) {
+ int to = 0;
+ if (p_len >= 0) {
+ to = p_len;
+ } else {
+ while (p_str[to] != 0 && p_str[to] != '.') {
+ to++;
+ }
+ }
+
+ int64_t integer = 0;
+ int64_t sign = 1;
+
+ for (int i = 0; i < to; i++) {
+ char c = p_str[i];
+ if (is_digit(c)) {
+ bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8')));
+ ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as integer, provided value is " + (sign == 1 ? "too big." : "too small."));
+ integer *= 10;
+ integer += c - '0';
+
+ } else if (c == '-' && integer == 0) {
+ sign = -sign;
+ } else if (c != ' ') {
+ break;
+ }
+ }
+
+ return integer * sign;
+}
+
+int64_t String::to_int(const wchar_t *p_str, int p_len) {
+ int to = 0;
+ if (p_len >= 0) {
+ to = p_len;
+ } else {
+ while (p_str[to] != 0 && p_str[to] != '.') {
+ to++;
+ }
+ }
+
+ int64_t integer = 0;
+ int64_t sign = 1;
+
+ for (int i = 0; i < to; i++) {
+ wchar_t c = p_str[i];
+ if (is_digit(c)) {
+ bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8')));
+ ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as integer, provided value is " + (sign == 1 ? "too big." : "too small."));
+ integer *= 10;
+ integer += c - '0';
+
+ } else if (c == '-' && integer == 0) {
+ sign = -sign;
+ } else if (c != ' ') {
+ break;
+ }
+ }
+
+ return integer * sign;
+}
+
+bool String::is_numeric() const {
+ if (length() == 0) {
+ return false;
+ }
+
+ int s = 0;
+ if (operator[](0) == '-') {
+ ++s;
+ }
+ bool dot = false;
+ for (int i = s; i < length(); i++) {
+ char32_t c = operator[](i);
+ if (c == '.') {
+ if (dot) {
+ return false;
+ }
+ dot = true;
+ } else if (c < '0' || c > '9') {
+ return false;
+ }
+ }
+
+ return true; // TODO: Use the parser below for this instead
+}
+
+template <class C>
+static double built_in_strtod(const C *string, /* A decimal ASCII floating-point number,
+ * optionally preceded by white space. Must
+ * have form "-I.FE-X", where I is the integer
+ * part of the mantissa, F is the fractional
+ * part of the mantissa, and X is the
+ * exponent. Either of the signs may be "+",
+ * "-", or omitted. Either I or F may be
+ * omitted, or both. The decimal point isn't
+ * necessary unless F is present. The "E" may
+ * actually be an "e". E and X may both be
+ * omitted (but not just one). */
+ C **endPtr = nullptr) /* If non-nullptr, store terminating Cacter's
+ * address here. */
+{
+ static const int maxExponent = 511; /* Largest possible base 10 exponent. Any
+ * exponent larger than this will already
+ * produce underflow or overflow, so there's
+ * no need to worry about additional digits.
+ */
+ static const double powersOf10[] = { /* Table giving binary powers of 10. Entry */
+ 10., /* is 10^2^i. Used to convert decimal */
+ 100., /* exponents into floating-point numbers. */
+ 1.0e4,
+ 1.0e8,
+ 1.0e16,
+ 1.0e32,
+ 1.0e64,
+ 1.0e128,
+ 1.0e256
+ };
+
+ bool sign, expSign = false;
+ double fraction, dblExp;
+ const double *d;
+ const C *p;
+ int c;
+ int exp = 0; /* Exponent read from "EX" field. */
+ int fracExp = 0; /* Exponent that derives from the fractional
+ * part. Under normal circumstances, it is
+ * the negative of the number of digits in F.
+ * However, if I is very long, the last digits
+ * of I get dropped (otherwise a long I with a
+ * large negative exponent could cause an
+ * unnecessary overflow on I alone). In this
+ * case, fracExp is incremented one for each
+ * dropped digit. */
+ int mantSize; /* Number of digits in mantissa. */
+ int decPt; /* Number of mantissa digits BEFORE decimal
+ * point. */
+ const C *pExp; /* Temporarily holds location of exponent in
+ * string. */
+
+ /*
+ * Strip off leading blanks and check for a sign.
+ */
+
+ p = string;
+ while (*p == ' ' || *p == '\t' || *p == '\n') {
+ p += 1;
+ }
+ if (*p == '-') {
+ sign = true;
+ p += 1;
+ } else {
+ if (*p == '+') {
+ p += 1;
+ }
+ sign = false;
+ }
+
+ /*
+ * Count the number of digits in the mantissa (including the decimal
+ * point), and also locate the decimal point.
+ */
+
+ decPt = -1;
+ for (mantSize = 0;; mantSize += 1) {
+ c = *p;
+ if (!is_digit(c)) {
+ if ((c != '.') || (decPt >= 0)) {
+ break;
+ }
+ decPt = mantSize;
+ }
+ p += 1;
+ }
+
+ /*
+ * Now suck up the digits in the mantissa. Use two integers to collect 9
+ * digits each (this is faster than using floating-point). If the mantissa
+ * has more than 18 digits, ignore the extras, since they can't affect the
+ * value anyway.
+ */
+
+ pExp = p;
+ p -= mantSize;
+ if (decPt < 0) {
+ decPt = mantSize;
+ } else {
+ mantSize -= 1; /* One of the digits was the point. */
+ }
+ if (mantSize > 18) {
+ fracExp = decPt - 18;
+ mantSize = 18;
+ } else {
+ fracExp = decPt - mantSize;
+ }
+ if (mantSize == 0) {
+ fraction = 0.0;
+ p = string;
+ goto done;
+ } else {
+ int frac1, frac2;
+
+ frac1 = 0;
+ for (; mantSize > 9; mantSize -= 1) {
+ c = *p;
+ p += 1;
+ if (c == '.') {
+ c = *p;
+ p += 1;
+ }
+ frac1 = 10 * frac1 + (c - '0');
+ }
+ frac2 = 0;
+ for (; mantSize > 0; mantSize -= 1) {
+ c = *p;
+ p += 1;
+ if (c == '.') {
+ c = *p;
+ p += 1;
+ }
+ frac2 = 10 * frac2 + (c - '0');
+ }
+ fraction = (1.0e9 * frac1) + frac2;
+ }
+
+ /*
+ * Skim off the exponent.
+ */
+
+ p = pExp;
+ if ((*p == 'E') || (*p == 'e')) {
+ p += 1;
+ if (*p == '-') {
+ expSign = true;
+ p += 1;
+ } else {
+ if (*p == '+') {
+ p += 1;
+ }
+ expSign = false;
+ }
+ if (!is_digit(char32_t(*p))) {
+ p = pExp;
+ goto done;
+ }
+ while (is_digit(char32_t(*p))) {
+ exp = exp * 10 + (*p - '0');
+ p += 1;
+ }
+ }
+ if (expSign) {
+ exp = fracExp - exp;
+ } else {
+ exp = fracExp + exp;
+ }
+
+ /*
+ * Generate a floating-point number that represents the exponent. Do this
+ * by processing the exponent one bit at a time to combine many powers of
+ * 2 of 10. Then combine the exponent with the fraction.
+ */
+
+ if (exp < 0) {
+ expSign = true;
+ exp = -exp;
+ } else {
+ expSign = false;
+ }
+
+ if (exp > maxExponent) {
+ exp = maxExponent;
+ WARN_PRINT("Exponent too high");
+ }
+ dblExp = 1.0;
+ for (d = powersOf10; exp != 0; exp >>= 1, ++d) {
+ if (exp & 01) {
+ dblExp *= *d;
+ }
+ }
+ if (expSign) {
+ fraction /= dblExp;
+ } else {
+ fraction *= dblExp;
+ }
+
+done:
+ if (endPtr != nullptr) {
+ *endPtr = (C *)p;
+ }
+
+ if (sign) {
+ return -fraction;
+ }
+ return fraction;
+}
+
+#define READING_SIGN 0
+#define READING_INT 1
+#define READING_DEC 2
+#define READING_EXP 3
+#define READING_DONE 4
+
+double String::to_float(const char *p_str) {
+ return built_in_strtod<char>(p_str);
+}
+
+double String::to_float(const char32_t *p_str, const char32_t **r_end) {
+ return built_in_strtod<char32_t>(p_str, (char32_t **)r_end);
+}
+
+double String::to_float(const wchar_t *p_str, const wchar_t **r_end) {
+ return built_in_strtod<wchar_t>(p_str, (wchar_t **)r_end);
+}
+
+int64_t String::to_int(const char32_t *p_str, int p_len, bool p_clamp) {
+ if (p_len == 0 || !p_str[0]) {
+ return 0;
+ }
+ ///@todo make more exact so saving and loading does not lose precision
+
+ int64_t integer = 0;
+ int64_t sign = 1;
+ int reading = READING_SIGN;
+
+ const char32_t *str = p_str;
+ const char32_t *limit = &p_str[p_len];
+
+ while (*str && reading != READING_DONE && str != limit) {
+ char32_t c = *(str++);
+ switch (reading) {
+ case READING_SIGN: {
+ if (is_digit(c)) {
+ reading = READING_INT;
+ // let it fallthrough
+ } else if (c == '-') {
+ sign = -1;
+ reading = READING_INT;
+ break;
+ } else if (c == '+') {
+ sign = 1;
+ reading = READING_INT;
+ break;
+ } else {
+ break;
+ }
+ [[fallthrough]];
+ }
+ case READING_INT: {
+ if (is_digit(c)) {
+ if (integer > INT64_MAX / 10) {
+ String number("");
+ str = p_str;
+ while (*str && str != limit) {
+ number += *(str++);
+ }
+ if (p_clamp) {
+ if (sign == 1) {
+ return INT64_MAX;
+ } else {
+ return INT64_MIN;
+ }
+ } else {
+ ERR_FAIL_V_MSG(sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + number + " as integer, provided value is " + (sign == 1 ? "too big." : "too small."));
+ }
+ }
+ integer *= 10;
+ integer += c - '0';
+ } else {
+ reading = READING_DONE;
+ }
+
+ } break;
+ }
+ }
+
+ return sign * integer;
+}
+
+double String::to_float() const {
+ if (is_empty()) {
+ return 0;
+ }
+ return built_in_strtod<char32_t>(get_data());
+}
+
+uint32_t String::hash(const char *p_cstr) {
+ uint32_t hashv = 5381;
+ uint32_t c;
+
+ while ((c = *p_cstr++)) {
+ hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */
+ }
+
+ return hashv;
+}
+
+uint32_t String::hash(const char *p_cstr, int p_len) {
+ uint32_t hashv = 5381;
+ for (int i = 0; i < p_len; i++) {
+ hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */
+ }
+
+ return hashv;
+}
+
+uint32_t String::hash(const wchar_t *p_cstr, int p_len) {
+ uint32_t hashv = 5381;
+ for (int i = 0; i < p_len; i++) {
+ hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */
+ }
+
+ return hashv;
+}
+
+uint32_t String::hash(const wchar_t *p_cstr) {
+ uint32_t hashv = 5381;
+ uint32_t c;
+
+ while ((c = *p_cstr++)) {
+ hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */
+ }
+
+ return hashv;
+}
+
+uint32_t String::hash(const char32_t *p_cstr, int p_len) {
+ uint32_t hashv = 5381;
+ for (int i = 0; i < p_len; i++) {
+ hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */
+ }
+
+ return hashv;
+}
+
+uint32_t String::hash(const char32_t *p_cstr) {
+ uint32_t hashv = 5381;
+ uint32_t c;
+
+ while ((c = *p_cstr++)) {
+ hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */
+ }
+
+ return hashv;
+}
+
+uint32_t String::hash() const {
+ /* simple djb2 hashing */
+
+ const char32_t *chr = get_data();
+ uint32_t hashv = 5381;
+ uint32_t c;
+
+ while ((c = *chr++)) {
+ hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */
+ }
+
+ return hashv;
+}
+
+uint64_t String::hash64() const {
+ /* simple djb2 hashing */
+
+ const char32_t *chr = get_data();
+ uint64_t hashv = 5381;
+ uint64_t c;
+
+ while ((c = *chr++)) {
+ hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */
+ }
+
+ return hashv;
+}
+
+String String::md5_text() const {
+ CharString cs = utf8();
+ unsigned char hash[16];
+ CryptoCore::md5((unsigned char *)cs.ptr(), cs.length(), hash);
+ return String::hex_encode_buffer(hash, 16);
+}
+
+String String::sha1_text() const {
+ CharString cs = utf8();
+ unsigned char hash[20];
+ CryptoCore::sha1((unsigned char *)cs.ptr(), cs.length(), hash);
+ return String::hex_encode_buffer(hash, 20);
+}
+
+String String::sha256_text() const {
+ CharString cs = utf8();
+ unsigned char hash[32];
+ CryptoCore::sha256((unsigned char *)cs.ptr(), cs.length(), hash);
+ return String::hex_encode_buffer(hash, 32);
+}
+
+Vector<uint8_t> String::md5_buffer() const {
+ CharString cs = utf8();
+ unsigned char hash[16];
+ CryptoCore::md5((unsigned char *)cs.ptr(), cs.length(), hash);
+
+ Vector<uint8_t> ret;
+ ret.resize(16);
+ for (int i = 0; i < 16; i++) {
+ ret.write[i] = hash[i];
+ }
+ return ret;
+}
+
+Vector<uint8_t> String::sha1_buffer() const {
+ CharString cs = utf8();
+ unsigned char hash[20];
+ CryptoCore::sha1((unsigned char *)cs.ptr(), cs.length(), hash);
+
+ Vector<uint8_t> ret;
+ ret.resize(20);
+ for (int i = 0; i < 20; i++) {
+ ret.write[i] = hash[i];
+ }
+
+ return ret;
+}
+
+Vector<uint8_t> String::sha256_buffer() const {
+ CharString cs = utf8();
+ unsigned char hash[32];
+ CryptoCore::sha256((unsigned char *)cs.ptr(), cs.length(), hash);
+
+ Vector<uint8_t> ret;
+ ret.resize(32);
+ for (int i = 0; i < 32; i++) {
+ ret.write[i] = hash[i];
+ }
+ return ret;
+}
+
+String String::insert(int p_at_pos, const String &p_string) const {
+ if (p_at_pos < 0) {
+ return *this;
+ }
+
+ if (p_at_pos > length()) {
+ p_at_pos = length();
+ }
+
+ String pre;
+ if (p_at_pos > 0) {
+ pre = substr(0, p_at_pos);
+ }
+
+ String post;
+ if (p_at_pos < length()) {
+ post = substr(p_at_pos, length() - p_at_pos);
+ }
+
+ return pre + p_string + post;
+}
+
+String String::substr(int p_from, int p_chars) const {
+ if (p_chars == -1) {
+ p_chars = length() - p_from;
+ }
+
+ if (is_empty() || p_from < 0 || p_from >= length() || p_chars <= 0) {
+ return "";
+ }
+
+ if ((p_from + p_chars) > length()) {
+ p_chars = length() - p_from;
+ }
+
+ if (p_from == 0 && p_chars >= length()) {
+ return String(*this);
+ }
+
+ String s = String();
+ s.copy_from_unchecked(&get_data()[p_from], p_chars);
+ return s;
+}
+
+int String::find(const String &p_str, int p_from) const {
+ if (p_from < 0) {
+ return -1;
+ }
+
+ const int src_len = p_str.length();
+
+ const int len = length();
+
+ if (src_len == 0 || len == 0) {
+ return -1; // won't find anything!
+ }
+
+ const char32_t *src = get_data();
+ const char32_t *str = p_str.get_data();
+
+ for (int i = p_from; i <= (len - src_len); i++) {
+ bool found = true;
+ for (int j = 0; j < src_len; j++) {
+ int read_pos = i + j;
+
+ if (read_pos >= len) {
+ ERR_PRINT("read_pos>=len");
+ return -1;
+ }
+
+ if (src[read_pos] != str[j]) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+int String::find(const char *p_str, int p_from) const {
+ if (p_from < 0) {
+ return -1;
+ }
+
+ const int len = length();
+
+ if (len == 0) {
+ return -1; // won't find anything!
+ }
+
+ const char32_t *src = get_data();
+
+ int src_len = 0;
+ while (p_str[src_len] != '\0') {
+ src_len++;
+ }
+
+ if (src_len == 1) {
+ const char32_t needle = p_str[0];
+
+ for (int i = p_from; i < len; i++) {
+ if (src[i] == needle) {
+ return i;
+ }
+ }
+
+ } else {
+ for (int i = p_from; i <= (len - src_len); i++) {
+ bool found = true;
+ for (int j = 0; j < src_len; j++) {
+ int read_pos = i + j;
+
+ if (read_pos >= len) {
+ ERR_PRINT("read_pos>=len");
+ return -1;
+ }
+
+ if (src[read_pos] != (char32_t)p_str[j]) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+}
+
+int String::find_char(const char32_t &p_char, int p_from) const {
+ return _cowdata.find(p_char, p_from);
+}
+
+int String::findmk(const Vector<String> &p_keys, int p_from, int *r_key) const {
+ if (p_from < 0) {
+ return -1;
+ }
+ if (p_keys.size() == 0) {
+ return -1;
+ }
+
+ //int src_len=p_str.length();
+ const String *keys = &p_keys[0];
+ int key_count = p_keys.size();
+ int len = length();
+
+ if (len == 0) {
+ return -1; // won't find anything!
+ }
+
+ const char32_t *src = get_data();
+
+ for (int i = p_from; i < len; i++) {
+ bool found = true;
+ for (int k = 0; k < key_count; k++) {
+ found = true;
+ if (r_key) {
+ *r_key = k;
+ }
+ const char32_t *cmp = keys[k].get_data();
+ int l = keys[k].length();
+
+ for (int j = 0; j < l; j++) {
+ int read_pos = i + j;
+
+ if (read_pos >= len) {
+ found = false;
+ break;
+ }
+
+ if (src[read_pos] != cmp[j]) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+int String::findn(const String &p_str, int p_from) const {
+ if (p_from < 0) {
+ return -1;
+ }
+
+ int src_len = p_str.length();
+
+ if (src_len == 0 || length() == 0) {
+ return -1; // won't find anything!
+ }
+
+ const char32_t *srcd = get_data();
+
+ for (int i = p_from; i <= (length() - src_len); i++) {
+ bool found = true;
+ for (int j = 0; j < src_len; j++) {
+ int read_pos = i + j;
+
+ if (read_pos >= length()) {
+ ERR_PRINT("read_pos>=length()");
+ return -1;
+ }
+
+ char32_t src = _find_lower(srcd[read_pos]);
+ char32_t dst = _find_lower(p_str[j]);
+
+ if (src != dst) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+int String::rfind(const String &p_str, int p_from) const {
+ // establish a limit
+ int limit = length() - p_str.length();
+ if (limit < 0) {
+ return -1;
+ }
+
+ // establish a starting point
+ if (p_from < 0) {
+ p_from = limit;
+ } else if (p_from > limit) {
+ p_from = limit;
+ }
+
+ int src_len = p_str.length();
+ int len = length();
+
+ if (src_len == 0 || len == 0) {
+ return -1; // won't find anything!
+ }
+
+ const char32_t *src = get_data();
+
+ for (int i = p_from; i >= 0; i--) {
+ bool found = true;
+ for (int j = 0; j < src_len; j++) {
+ int read_pos = i + j;
+
+ if (read_pos >= len) {
+ ERR_PRINT("read_pos>=len");
+ return -1;
+ }
+
+ if (src[read_pos] != p_str[j]) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+int String::rfindn(const String &p_str, int p_from) const {
+ // establish a limit
+ int limit = length() - p_str.length();
+ if (limit < 0) {
+ return -1;
+ }
+
+ // establish a starting point
+ if (p_from < 0) {
+ p_from = limit;
+ } else if (p_from > limit) {
+ p_from = limit;
+ }
+
+ int src_len = p_str.length();
+ int len = length();
+
+ if (src_len == 0 || len == 0) {
+ return -1; // won't find anything!
+ }
+
+ const char32_t *src = get_data();
+
+ for (int i = p_from; i >= 0; i--) {
+ bool found = true;
+ for (int j = 0; j < src_len; j++) {
+ int read_pos = i + j;
+
+ if (read_pos >= len) {
+ ERR_PRINT("read_pos>=len");
+ return -1;
+ }
+
+ char32_t srcc = _find_lower(src[read_pos]);
+ char32_t dstc = _find_lower(p_str[j]);
+
+ if (srcc != dstc) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+bool String::ends_with(const String &p_string) const {
+ int l = p_string.length();
+ if (l > length()) {
+ return false;
+ }
+
+ if (l == 0) {
+ return true;
+ }
+
+ const char32_t *p = &p_string[0];
+ const char32_t *s = &operator[](length() - l);
+
+ for (int i = 0; i < l; i++) {
+ if (p[i] != s[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool String::begins_with(const String &p_string) const {
+ int l = p_string.length();
+ if (l > length()) {
+ return false;
+ }
+
+ if (l == 0) {
+ return true;
+ }
+
+ const char32_t *p = &p_string[0];
+ const char32_t *s = &operator[](0);
+
+ for (int i = 0; i < l; i++) {
+ if (p[i] != s[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool String::begins_with(const char *p_string) const {
+ int l = length();
+ if (l == 0 || !p_string) {
+ return false;
+ }
+
+ const char32_t *str = &operator[](0);
+ int i = 0;
+
+ while (*p_string && i < l) {
+ if ((char32_t)*p_string != str[i]) {
+ return false;
+ }
+ i++;
+ p_string++;
+ }
+
+ return *p_string == 0;
+}
+
+bool String::is_enclosed_in(const String &p_string) const {
+ return begins_with(p_string) && ends_with(p_string);
+}
+
+bool String::is_subsequence_of(const String &p_string) const {
+ return _base_is_subsequence_of(p_string, false);
+}
+
+bool String::is_subsequence_ofi(const String &p_string) const {
+ return _base_is_subsequence_of(p_string, true);
+}
+
+bool String::is_quoted() const {
+ return is_enclosed_in("\"") || is_enclosed_in("'");
+}
+
+int String::_count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const {
+ if (p_string.is_empty()) {
+ return 0;
+ }
+ int len = length();
+ int slen = p_string.length();
+ if (len < slen) {
+ return 0;
+ }
+ String str;
+ if (p_from >= 0 && p_to >= 0) {
+ if (p_to == 0) {
+ p_to = len;
+ } else if (p_from >= p_to) {
+ return 0;
+ }
+ if (p_from == 0 && p_to == len) {
+ str = String();
+ str.copy_from_unchecked(&get_data()[0], len);
+ } else {
+ str = substr(p_from, p_to - p_from);
+ }
+ } else {
+ return 0;
+ }
+ int c = 0;
+ int idx = -1;
+ do {
+ idx = p_case_insensitive ? str.findn(p_string) : str.find(p_string);
+ if (idx != -1) {
+ str = str.substr(idx + slen, str.length() - slen);
+ ++c;
+ }
+ } while (idx != -1);
+ return c;
+}
+
+int String::count(const String &p_string, int p_from, int p_to) const {
+ return _count(p_string, p_from, p_to, false);
+}
+
+int String::countn(const String &p_string, int p_from, int p_to) const {
+ return _count(p_string, p_from, p_to, true);
+}
+
+bool String::_base_is_subsequence_of(const String &p_string, bool case_insensitive) const {
+ int len = length();
+ if (len == 0) {
+ // Technically an empty string is subsequence of any string
+ return true;
+ }
+
+ if (len > p_string.length()) {
+ return false;
+ }
+
+ const char32_t *src = &operator[](0);
+ const char32_t *tgt = &p_string[0];
+
+ for (; *src && *tgt; tgt++) {
+ bool match = false;
+ if (case_insensitive) {
+ char32_t srcc = _find_lower(*src);
+ char32_t tgtc = _find_lower(*tgt);
+ match = srcc == tgtc;
+ } else {
+ match = *src == *tgt;
+ }
+ if (match) {
+ src++;
+ if (!*src) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+Vector<String> String::bigrams() const {
+ int n_pairs = length() - 1;
+ Vector<String> b;
+ if (n_pairs <= 0) {
+ return b;
+ }
+ b.resize(n_pairs);
+ for (int i = 0; i < n_pairs; i++) {
+ b.write[i] = substr(i, 2);
+ }
+ return b;
+}
+
+// Similarity according to Sorensen-Dice coefficient
+float String::similarity(const String &p_string) const {
+ if (operator==(p_string)) {
+ // Equal strings are totally similar
+ return 1.0f;
+ }
+ if (length() < 2 || p_string.length() < 2) {
+ // No way to calculate similarity without a single bigram
+ return 0.0f;
+ }
+
+ Vector<String> src_bigrams = bigrams();
+ Vector<String> tgt_bigrams = p_string.bigrams();
+
+ int src_size = src_bigrams.size();
+ int tgt_size = tgt_bigrams.size();
+
+ int sum = src_size + tgt_size;
+ int inter = 0;
+ for (int i = 0; i < src_size; i++) {
+ for (int j = 0; j < tgt_size; j++) {
+ if (src_bigrams[i] == tgt_bigrams[j]) {
+ inter++;
+ break;
+ }
+ }
+ }
+
+ return (2.0f * inter) / sum;
+}
+
+static bool _wildcard_match(const char32_t *p_pattern, const char32_t *p_string, bool p_case_sensitive) {
+ switch (*p_pattern) {
+ case '\0':
+ return !*p_string;
+ case '*':
+ return _wildcard_match(p_pattern + 1, p_string, p_case_sensitive) || (*p_string && _wildcard_match(p_pattern, p_string + 1, p_case_sensitive));
+ case '?':
+ return *p_string && (*p_string != '.') && _wildcard_match(p_pattern + 1, p_string + 1, p_case_sensitive);
+ default:
+
+ return (p_case_sensitive ? (*p_string == *p_pattern) : (_find_upper(*p_string) == _find_upper(*p_pattern))) && _wildcard_match(p_pattern + 1, p_string + 1, p_case_sensitive);
+ }
+}
+
+bool String::match(const String &p_wildcard) const {
+ if (!p_wildcard.length() || !length()) {
+ return false;
+ }
+
+ return _wildcard_match(p_wildcard.get_data(), get_data(), true);
+}
+
+bool String::matchn(const String &p_wildcard) const {
+ if (!p_wildcard.length() || !length()) {
+ return false;
+ }
+ return _wildcard_match(p_wildcard.get_data(), get_data(), false);
+}
+
+String String::format(const Variant &values, String placeholder) const {
+ String new_string = String(this->ptr());
+
+ if (values.get_type() == Variant::ARRAY) {
+ Array values_arr = values;
+
+ for (int i = 0; i < values_arr.size(); i++) {
+ String i_as_str = String::num_int64(i);
+
+ if (values_arr[i].get_type() == Variant::ARRAY) { //Array in Array structure [["name","RobotGuy"],[0,"godot"],["strength",9000.91]]
+ Array value_arr = values_arr[i];
+
+ if (value_arr.size() == 2) {
+ Variant v_key = value_arr[0];
+ String key = v_key;
+
+ Variant v_val = value_arr[1];
+ String val = v_val;
+
+ new_string = new_string.replace(placeholder.replace("_", key), val);
+ } else {
+ ERR_PRINT(String("STRING.format Inner Array size != 2 ").ascii().get_data());
+ }
+ } else { //Array structure ["RobotGuy","Logis","rookie"]
+ Variant v_val = values_arr[i];
+ String val = v_val;
+
+ if (placeholder.find("_") > -1) {
+ new_string = new_string.replace(placeholder.replace("_", i_as_str), val);
+ } else {
+ new_string = new_string.replace_first(placeholder, val);
+ }
+ }
+ }
+ } else if (values.get_type() == Variant::DICTIONARY) {
+ Dictionary d = values;
+ List<Variant> keys;
+ d.get_key_list(&keys);
+
+ for (const Variant &key : keys) {
+ new_string = new_string.replace(placeholder.replace("_", key), d[key]);
+ }
+ } else {
+ ERR_PRINT(String("Invalid type: use Array or Dictionary.").ascii().get_data());
+ }
+
+ return new_string;
+}
+
+String String::replace(const String &p_key, const String &p_with) const {
+ String new_string;
+ int search_from = 0;
+ int result = 0;
+
+ while ((result = find(p_key, search_from)) >= 0) {
+ new_string += substr(search_from, result - search_from);
+ new_string += p_with;
+ search_from = result + p_key.length();
+ }
+
+ if (search_from == 0) {
+ return *this;
+ }
+
+ new_string += substr(search_from, length() - search_from);
+
+ return new_string;
+}
+
+String String::replace(const char *p_key, const char *p_with) const {
+ String new_string;
+ int search_from = 0;
+ int result = 0;
+
+ while ((result = find(p_key, search_from)) >= 0) {
+ new_string += substr(search_from, result - search_from);
+ new_string += p_with;
+ int k = 0;
+ while (p_key[k] != '\0') {
+ k++;
+ }
+ search_from = result + k;
+ }
+
+ if (search_from == 0) {
+ return *this;
+ }
+
+ new_string += substr(search_from, length() - search_from);
+
+ return new_string;
+}
+
+String String::replace_first(const String &p_key, const String &p_with) const {
+ int pos = find(p_key);
+ if (pos >= 0) {
+ return substr(0, pos) + p_with + substr(pos + p_key.length(), length());
+ }
+
+ return *this;
+}
+
+String String::replacen(const String &p_key, const String &p_with) const {
+ String new_string;
+ int search_from = 0;
+ int result = 0;
+
+ while ((result = findn(p_key, search_from)) >= 0) {
+ new_string += substr(search_from, result - search_from);
+ new_string += p_with;
+ search_from = result + p_key.length();
+ }
+
+ if (search_from == 0) {
+ return *this;
+ }
+
+ new_string += substr(search_from, length() - search_from);
+ return new_string;
+}
+
+String String::repeat(int p_count) const {
+ ERR_FAIL_COND_V_MSG(p_count < 0, "", "Parameter count should be a positive number.");
+
+ String new_string;
+ const char32_t *src = this->get_data();
+
+ new_string.resize(length() * p_count + 1);
+ new_string[length() * p_count] = 0;
+
+ for (int i = 0; i < p_count; i++) {
+ for (int j = 0; j < length(); j++) {
+ new_string[i * length() + j] = src[j];
+ }
+ }
+
+ return new_string;
+}
+
+String String::left(int p_pos) const {
+ if (p_pos < 0) {
+ p_pos = length() + p_pos;
+ }
+
+ if (p_pos <= 0) {
+ return "";
+ }
+
+ if (p_pos >= length()) {
+ return *this;
+ }
+
+ return substr(0, p_pos);
+}
+
+String String::right(int p_pos) const {
+ if (p_pos < 0) {
+ p_pos = length() + p_pos;
+ }
+
+ if (p_pos <= 0) {
+ return "";
+ }
+
+ if (p_pos >= length()) {
+ return *this;
+ }
+
+ return substr(length() - p_pos);
+}
+
+char32_t String::unicode_at(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx, length(), 0);
+ return operator[](p_idx);
+}
+
+String String::dedent() const {
+ String new_string;
+ String indent;
+ bool has_indent = false;
+ bool has_text = false;
+ int line_start = 0;
+ int indent_stop = -1;
+
+ for (int i = 0; i < length(); i++) {
+ char32_t c = operator[](i);
+ if (c == '\n') {
+ if (has_text) {
+ new_string += substr(indent_stop, i - indent_stop);
+ }
+ new_string += "\n";
+ has_text = false;
+ line_start = i + 1;
+ indent_stop = -1;
+ } else if (!has_text) {
+ if (c > 32) {
+ has_text = true;
+ if (!has_indent) {
+ has_indent = true;
+ indent = substr(line_start, i - line_start);
+ indent_stop = i;
+ }
+ }
+ if (has_indent && indent_stop < 0) {
+ int j = i - line_start;
+ if (j >= indent.length() || c != indent[j]) {
+ indent_stop = i;
+ }
+ }
+ }
+ }
+
+ if (has_text) {
+ new_string += substr(indent_stop, length() - indent_stop);
+ }
+
+ return new_string;
+}
+
+String String::strip_edges(bool left, bool right) const {
+ int len = length();
+ int beg = 0, end = len;
+
+ if (left) {
+ for (int i = 0; i < len; i++) {
+ if (operator[](i) <= 32) {
+ beg++;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (right) {
+ for (int i = len - 1; i >= 0; i--) {
+ if (operator[](i) <= 32) {
+ end--;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (beg == 0 && end == len) {
+ return *this;
+ }
+
+ return substr(beg, end - beg);
+}
+
+String String::strip_escapes() const {
+ String new_string;
+ for (int i = 0; i < length(); i++) {
+ // Escape characters on first page of the ASCII table, before 32 (Space).
+ if (operator[](i) < 32) {
+ continue;
+ }
+ new_string += operator[](i);
+ }
+
+ return new_string;
+}
+
+String String::lstrip(const String &p_chars) const {
+ int len = length();
+ int beg;
+
+ for (beg = 0; beg < len; beg++) {
+ if (p_chars.find_char(get(beg)) == -1) {
+ break;
+ }
+ }
+
+ if (beg == 0) {
+ return *this;
+ }
+
+ return substr(beg, len - beg);
+}
+
+String String::rstrip(const String &p_chars) const {
+ int len = length();
+ int end;
+
+ for (end = len - 1; end >= 0; end--) {
+ if (p_chars.find_char(get(end)) == -1) {
+ break;
+ }
+ }
+
+ if (end == len - 1) {
+ return *this;
+ }
+
+ return substr(0, end + 1);
+}
+
+String String::simplify_path() const {
+ String s = *this;
+ String drive;
+ if (s.begins_with("local://")) {
+ drive = "local://";
+ s = s.substr(8, s.length());
+ } else if (s.begins_with("res://")) {
+ drive = "res://";
+ s = s.substr(6, s.length());
+ } else if (s.begins_with("user://")) {
+ drive = "user://";
+ s = s.substr(7, s.length());
+ } else if (s.begins_with("/") || s.begins_with("\\")) {
+ drive = s.substr(0, 1);
+ s = s.substr(1, s.length() - 1);
+ } else {
+ int p = s.find(":/");
+ if (p == -1) {
+ p = s.find(":\\");
+ }
+ if (p != -1 && p < s.find("/")) {
+ drive = s.substr(0, p + 2);
+ s = s.substr(p + 2, s.length());
+ }
+ }
+
+ s = s.replace("\\", "/");
+ while (true) { // in case of using 2 or more slash
+ String compare = s.replace("//", "/");
+ if (s == compare) {
+ break;
+ } else {
+ s = compare;
+ }
+ }
+ Vector<String> dirs = s.split("/", false);
+
+ for (int i = 0; i < dirs.size(); i++) {
+ String d = dirs[i];
+ if (d == ".") {
+ dirs.remove(i);
+ i--;
+ } else if (d == "..") {
+ if (i == 0) {
+ dirs.remove(i);
+ i--;
+ } else {
+ dirs.remove(i);
+ dirs.remove(i - 1);
+ i -= 2;
+ }
+ }
+ }
+
+ s = "";
+
+ for (int i = 0; i < dirs.size(); i++) {
+ if (i > 0) {
+ s += "/";
+ }
+ s += dirs[i];
+ }
+
+ return drive + s;
+}
+
+static int _humanize_digits(int p_num) {
+ if (p_num < 100) {
+ return 2;
+ } else if (p_num < 1024) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+String String::humanize_size(uint64_t p_size) {
+ uint64_t _div = 1;
+ Vector<String> prefixes;
+ prefixes.push_back(RTR("B"));
+ prefixes.push_back(RTR("KiB"));
+ prefixes.push_back(RTR("MiB"));
+ prefixes.push_back(RTR("GiB"));
+ prefixes.push_back(RTR("TiB"));
+ prefixes.push_back(RTR("PiB"));
+ prefixes.push_back(RTR("EiB"));
+
+ int prefix_idx = 0;
+
+ while (prefix_idx < prefixes.size() - 1 && p_size > (_div * 1024)) {
+ _div *= 1024;
+ prefix_idx++;
+ }
+
+ const int digits = prefix_idx > 0 ? _humanize_digits(p_size / _div) : 0;
+ const double divisor = prefix_idx > 0 ? _div : 1;
+
+ return String::num(p_size / divisor).pad_decimals(digits) + " " + prefixes[prefix_idx];
+}
+
+bool String::is_absolute_path() const {
+ if (length() > 1) {
+ return (operator[](0) == '/' || operator[](0) == '\\' || find(":/") != -1 || find(":\\") != -1);
+ } else if ((length()) == 1) {
+ return (operator[](0) == '/' || operator[](0) == '\\');
+ } else {
+ return false;
+ }
+}
+
+bool String::is_valid_identifier() const {
+ int len = length();
+
+ if (len == 0) {
+ return false;
+ }
+
+ const char32_t *str = &operator[](0);
+
+ for (int i = 0; i < len; i++) {
+ if (i == 0) {
+ if (is_digit(str[0])) {
+ return false; // no start with number plz
+ }
+ }
+
+ bool valid_char = is_digit(str[i]) || is_lower_case(str[i]) || is_upper_case(str[i]) || str[i] == '_';
+
+ if (!valid_char) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool String::is_valid_string() const {
+ int l = length();
+ const char32_t *src = get_data();
+ bool valid = true;
+ for (int i = 0; i < l; i++) {
+ valid = valid && (src[i] < 0xd800 || (src[i] > 0xdfff && src[i] <= 0x10ffff));
+ }
+ return valid;
+}
+
+String String::uri_encode() const {
+ const CharString temp = utf8();
+ String res;
+ for (int i = 0; i < temp.length(); ++i) {
+ char ord = temp[i];
+ if (ord == '.' || ord == '-' || ord == '_' || ord == '~' || is_lower_case(ord) || is_upper_case(ord) || is_digit(ord)) {
+ res += ord;
+ } else {
+ char h_Val[3];
+#if defined(__GNUC__) || defined(_MSC_VER)
+ snprintf(h_Val, 3, "%02hhX", ord);
+#else
+ sprintf(h_Val, "%02hhX", ord);
+#endif
+ res += "%";
+ res += h_Val;
+ }
+ }
+ return res;
+}
+
+String String::uri_decode() const {
+ CharString src = utf8();
+ CharString res;
+ for (int i = 0; i < src.length(); ++i) {
+ if (src[i] == '%' && i + 2 < src.length()) {
+ char ord1 = src[i + 1];
+ if (is_digit(ord1) || is_upper_case(ord1)) {
+ char ord2 = src[i + 2];
+ if (is_digit(ord2) || is_upper_case(ord2)) {
+ char bytes[3] = { (char)ord1, (char)ord2, 0 };
+ res += (char)strtol(bytes, nullptr, 16);
+ i += 2;
+ }
+ } else {
+ res += src[i];
+ }
+ } else if (src[i] == '+') {
+ res += ' ';
+ } else {
+ res += src[i];
+ }
+ }
+ return String::utf8(res);
+}
+
+String String::c_unescape() const {
+ String escaped = *this;
+ escaped = escaped.replace("\\a", "\a");
+ escaped = escaped.replace("\\b", "\b");
+ escaped = escaped.replace("\\f", "\f");
+ escaped = escaped.replace("\\n", "\n");
+ escaped = escaped.replace("\\r", "\r");
+ escaped = escaped.replace("\\t", "\t");
+ escaped = escaped.replace("\\v", "\v");
+ escaped = escaped.replace("\\'", "\'");
+ escaped = escaped.replace("\\\"", "\"");
+ escaped = escaped.replace("\\?", "\?");
+ escaped = escaped.replace("\\\\", "\\");
+
+ return escaped;
+}
+
+String String::c_escape() const {
+ String escaped = *this;
+ escaped = escaped.replace("\\", "\\\\");
+ escaped = escaped.replace("\a", "\\a");
+ escaped = escaped.replace("\b", "\\b");
+ escaped = escaped.replace("\f", "\\f");
+ escaped = escaped.replace("\n", "\\n");
+ escaped = escaped.replace("\r", "\\r");
+ escaped = escaped.replace("\t", "\\t");
+ escaped = escaped.replace("\v", "\\v");
+ escaped = escaped.replace("\'", "\\'");
+ escaped = escaped.replace("\?", "\\?");
+ escaped = escaped.replace("\"", "\\\"");
+
+ return escaped;
+}
+
+String String::c_escape_multiline() const {
+ String escaped = *this;
+ escaped = escaped.replace("\\", "\\\\");
+ escaped = escaped.replace("\"", "\\\"");
+
+ return escaped;
+}
+
+String String::json_escape() const {
+ String escaped = *this;
+ escaped = escaped.replace("\\", "\\\\");
+ escaped = escaped.replace("\b", "\\b");
+ escaped = escaped.replace("\f", "\\f");
+ escaped = escaped.replace("\n", "\\n");
+ escaped = escaped.replace("\r", "\\r");
+ escaped = escaped.replace("\t", "\\t");
+ escaped = escaped.replace("\v", "\\v");
+ escaped = escaped.replace("\"", "\\\"");
+
+ return escaped;
+}
+
+String String::xml_escape(bool p_escape_quotes) const {
+ String str = *this;
+ str = str.replace("&", "&amp;");
+ str = str.replace("<", "&lt;");
+ str = str.replace(">", "&gt;");
+ if (p_escape_quotes) {
+ str = str.replace("'", "&apos;");
+ str = str.replace("\"", "&quot;");
+ }
+ /*
+for (int i=1;i<32;i++) {
+ char chr[2]={i,0};
+ str=str.replace(chr,"&#"+String::num(i)+";");
+}*/
+ return str;
+}
+
+static _FORCE_INLINE_ int _xml_unescape(const char32_t *p_src, int p_src_len, char32_t *p_dst) {
+ int len = 0;
+ while (p_src_len) {
+ if (*p_src == '&') {
+ int eat = 0;
+
+ if (p_src_len >= 4 && p_src[1] == '#') {
+ char32_t c = 0;
+ bool overflow = false;
+ if (p_src[2] == 'x') {
+ // Hex entity &#x<num>;
+ for (int i = 3; i < p_src_len; i++) {
+ eat = i + 1;
+ char32_t ct = p_src[i];
+ if (ct == ';') {
+ break;
+ } else if (is_digit(ct)) {
+ ct = ct - '0';
+ } else if (ct >= 'a' && ct <= 'f') {
+ ct = (ct - 'a') + 10;
+ } else if (ct >= 'A' && ct <= 'F') {
+ ct = (ct - 'A') + 10;
+ } else {
+ break;
+ }
+ if (c > (UINT32_MAX >> 4)) {
+ overflow = true;
+ break;
+ }
+ c <<= 4;
+ c |= ct;
+ }
+ } else {
+ // Decimal entity &#<num>;
+ for (int i = 2; i < p_src_len; i++) {
+ eat = i + 1;
+ char32_t ct = p_src[i];
+ if (ct == ';' || ct < '0' || ct > '9') {
+ break;
+ }
+ }
+ if (p_src[eat - 1] == ';') {
+ int64_t val = String::to_int(p_src + 2, eat - 3);
+ if (val > 0 && val <= UINT32_MAX) {
+ c = (char32_t)val;
+ } else {
+ overflow = true;
+ }
+ }
+ }
+
+ // Value must be non-zero, in the range of char32_t,
+ // actually end with ';'. If invalid, leave the entity as-is
+ if (c == '\0' || overflow || p_src[eat - 1] != ';') {
+ eat = 1;
+ c = *p_src;
+ }
+ if (p_dst) {
+ *p_dst = c;
+ }
+
+ } else if (p_src_len >= 4 && p_src[1] == 'g' && p_src[2] == 't' && p_src[3] == ';') {
+ if (p_dst) {
+ *p_dst = '>';
+ }
+ eat = 4;
+ } else if (p_src_len >= 4 && p_src[1] == 'l' && p_src[2] == 't' && p_src[3] == ';') {
+ if (p_dst) {
+ *p_dst = '<';
+ }
+ eat = 4;
+ } else if (p_src_len >= 5 && p_src[1] == 'a' && p_src[2] == 'm' && p_src[3] == 'p' && p_src[4] == ';') {
+ if (p_dst) {
+ *p_dst = '&';
+ }
+ eat = 5;
+ } else if (p_src_len >= 6 && p_src[1] == 'q' && p_src[2] == 'u' && p_src[3] == 'o' && p_src[4] == 't' && p_src[5] == ';') {
+ if (p_dst) {
+ *p_dst = '"';
+ }
+ eat = 6;
+ } else if (p_src_len >= 6 && p_src[1] == 'a' && p_src[2] == 'p' && p_src[3] == 'o' && p_src[4] == 's' && p_src[5] == ';') {
+ if (p_dst) {
+ *p_dst = '\'';
+ }
+ eat = 6;
+ } else {
+ if (p_dst) {
+ *p_dst = *p_src;
+ }
+ eat = 1;
+ }
+
+ if (p_dst) {
+ p_dst++;
+ }
+
+ len++;
+ p_src += eat;
+ p_src_len -= eat;
+ } else {
+ if (p_dst) {
+ *p_dst = *p_src;
+ p_dst++;
+ }
+ len++;
+ p_src++;
+ p_src_len--;
+ }
+ }
+
+ return len;
+}
+
+String String::xml_unescape() const {
+ String str;
+ int l = length();
+ int len = _xml_unescape(get_data(), l, nullptr);
+ if (len == 0) {
+ return String();
+ }
+ str.resize(len + 1);
+ _xml_unescape(get_data(), l, str.ptrw());
+ str[len] = 0;
+ return str;
+}
+
+String String::pad_decimals(int p_digits) const {
+ String s = *this;
+ int c = s.find(".");
+
+ if (c == -1) {
+ if (p_digits <= 0) {
+ return s;
+ }
+ s += ".";
+ c = s.length() - 1;
+ } else {
+ if (p_digits <= 0) {
+ return s.substr(0, c);
+ }
+ }
+
+ if (s.length() - (c + 1) > p_digits) {
+ s = s.substr(0, c + p_digits + 1);
+ } else {
+ while (s.length() - (c + 1) < p_digits) {
+ s += "0";
+ }
+ }
+ return s;
+}
+
+String String::pad_zeros(int p_digits) const {
+ String s = *this;
+ int end = s.find(".");
+
+ if (end == -1) {
+ end = s.length();
+ }
+
+ if (end == 0) {
+ return s;
+ }
+
+ int begin = 0;
+
+ while (begin < end && (s[begin] < '0' || s[begin] > '9')) {
+ begin++;
+ }
+
+ if (begin >= end) {
+ return s;
+ }
+
+ while (end - begin < p_digits) {
+ s = s.insert(begin, "0");
+ end++;
+ }
+
+ return s;
+}
+
+String String::trim_prefix(const String &p_prefix) const {
+ String s = *this;
+ if (s.begins_with(p_prefix)) {
+ return s.substr(p_prefix.length(), s.length() - p_prefix.length());
+ }
+ return s;
+}
+
+String String::trim_suffix(const String &p_suffix) const {
+ String s = *this;
+ if (s.ends_with(p_suffix)) {
+ return s.substr(0, s.length() - p_suffix.length());
+ }
+ return s;
+}
+
+bool String::is_valid_int() const {
+ int len = length();
+
+ if (len == 0) {
+ return false;
+ }
+
+ int from = 0;
+ if (len != 1 && (operator[](0) == '+' || operator[](0) == '-')) {
+ from++;
+ }
+
+ for (int i = from; i < len; i++) {
+ if (operator[](i) < '0' || operator[](i) > '9') {
+ return false; // no start with number plz
+ }
+ }
+
+ return true;
+}
+
+bool String::is_valid_hex_number(bool p_with_prefix) const {
+ int len = length();
+
+ if (len == 0) {
+ return false;
+ }
+
+ int from = 0;
+ if (len != 1 && (operator[](0) == '+' || operator[](0) == '-')) {
+ from++;
+ }
+
+ if (p_with_prefix) {
+ if (len < 3) {
+ return false;
+ }
+ if (operator[](from) != '0' || operator[](from + 1) != 'x') {
+ return false;
+ }
+ from += 2;
+ }
+
+ for (int i = from; i < len; i++) {
+ char32_t c = operator[](i);
+ if (is_hex_digit(c)) {
+ continue;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool String::is_valid_float() const {
+ int len = length();
+
+ if (len == 0) {
+ return false;
+ }
+
+ int from = 0;
+ if (operator[](0) == '+' || operator[](0) == '-') {
+ from++;
+ }
+
+ bool exponent_found = false;
+ bool period_found = false;
+ bool sign_found = false;
+ bool exponent_values_found = false;
+ bool numbers_found = false;
+
+ for (int i = from; i < len; i++) {
+ if (is_digit(operator[](i))) {
+ if (exponent_found) {
+ exponent_values_found = true;
+ } else {
+ numbers_found = true;
+ }
+ } else if (numbers_found && !exponent_found && operator[](i) == 'e') {
+ exponent_found = true;
+ } else if (!period_found && !exponent_found && operator[](i) == '.') {
+ period_found = true;
+ } else if ((operator[](i) == '-' || operator[](i) == '+') && exponent_found && !exponent_values_found && !sign_found) {
+ sign_found = true;
+ } else {
+ return false; // no start with number plz
+ }
+ }
+
+ return numbers_found;
+}
+
+String String::path_to_file(const String &p_path) const {
+ // Don't get base dir for src, this is expected to be a dir already.
+ String src = this->replace("\\", "/");
+ String dst = p_path.replace("\\", "/").get_base_dir();
+ String rel = src.path_to(dst);
+ if (rel == dst) { // failed
+ return p_path;
+ } else {
+ return rel + p_path.get_file();
+ }
+}
+
+String String::path_to(const String &p_path) const {
+ String src = this->replace("\\", "/");
+ String dst = p_path.replace("\\", "/");
+ if (!src.ends_with("/")) {
+ src += "/";
+ }
+ if (!dst.ends_with("/")) {
+ dst += "/";
+ }
+
+ String base;
+
+ if (src.begins_with("res://") && dst.begins_with("res://")) {
+ base = "res:/";
+ src = src.replace("res://", "/");
+ dst = dst.replace("res://", "/");
+
+ } else if (src.begins_with("user://") && dst.begins_with("user://")) {
+ base = "user:/";
+ src = src.replace("user://", "/");
+ dst = dst.replace("user://", "/");
+
+ } else if (src.begins_with("/") && dst.begins_with("/")) {
+ //nothing
+ } else {
+ //dos style
+ String src_begin = src.get_slicec('/', 0);
+ String dst_begin = dst.get_slicec('/', 0);
+
+ if (src_begin != dst_begin) {
+ return p_path; //impossible to do this
+ }
+
+ base = src_begin;
+ src = src.substr(src_begin.length(), src.length());
+ dst = dst.substr(dst_begin.length(), dst.length());
+ }
+
+ //remove leading and trailing slash and split
+ Vector<String> src_dirs = src.substr(1, src.length() - 2).split("/");
+ Vector<String> dst_dirs = dst.substr(1, dst.length() - 2).split("/");
+
+ //find common parent
+ int common_parent = 0;
+
+ while (true) {
+ if (src_dirs.size() == common_parent) {
+ break;
+ }
+ if (dst_dirs.size() == common_parent) {
+ break;
+ }
+ if (src_dirs[common_parent] != dst_dirs[common_parent]) {
+ break;
+ }
+ common_parent++;
+ }
+
+ common_parent--;
+
+ String dir;
+
+ for (int i = src_dirs.size() - 1; i > common_parent; i--) {
+ dir += "../";
+ }
+
+ for (int i = common_parent + 1; i < dst_dirs.size(); i++) {
+ dir += dst_dirs[i] + "/";
+ }
+
+ if (dir.length() == 0) {
+ dir = "./";
+ }
+ return dir;
+}
+
+bool String::is_valid_html_color() const {
+ return Color::html_is_valid(*this);
+}
+
+bool String::is_valid_filename() const {
+ String stripped = strip_edges();
+ if (*this != stripped) {
+ return false;
+ }
+
+ if (stripped == String()) {
+ return false;
+ }
+
+ return !(find(":") != -1 || find("/") != -1 || find("\\") != -1 || find("?") != -1 || find("*") != -1 || find("\"") != -1 || find("|") != -1 || find("%") != -1 || find("<") != -1 || find(">") != -1);
+}
+
+bool String::is_valid_ip_address() const {
+ if (find(":") >= 0) {
+ Vector<String> ip = split(":");
+ for (int i = 0; i < ip.size(); i++) {
+ String n = ip[i];
+ if (n.is_empty()) {
+ continue;
+ }
+ if (n.is_valid_hex_number(false)) {
+ int64_t nint = n.hex_to_int();
+ if (nint < 0 || nint > 0xffff) {
+ return false;
+ }
+ continue;
+ }
+ if (!n.is_valid_ip_address()) {
+ return false;
+ }
+ }
+
+ } else {
+ Vector<String> ip = split(".");
+ if (ip.size() != 4) {
+ return false;
+ }
+ for (int i = 0; i < ip.size(); i++) {
+ String n = ip[i];
+ if (!n.is_valid_int()) {
+ return false;
+ }
+ int val = n.to_int();
+ if (val < 0 || val > 255) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool String::is_resource_file() const {
+ return begins_with("res://") && find("::") == -1;
+}
+
+bool String::is_relative_path() const {
+ return !is_absolute_path();
+}
+
+String String::get_base_dir() const {
+ int end = 0;
+
+ // url scheme style base
+ int basepos = find("://");
+ if (basepos != -1) {
+ end = basepos + 3;
+ }
+
+ // windows top level directory base
+ if (end == 0) {
+ basepos = find(":/");
+ if (basepos == -1) {
+ basepos = find(":\\");
+ }
+ if (basepos != -1) {
+ end = basepos + 2;
+ }
+ }
+
+ // unix root directory base
+ if (end == 0) {
+ if (begins_with("/")) {
+ end = 1;
+ }
+ }
+
+ String rs;
+ String base;
+ if (end != 0) {
+ rs = substr(end, length());
+ base = substr(0, end);
+ } else {
+ rs = *this;
+ }
+
+ int sep = MAX(rs.rfind("/"), rs.rfind("\\"));
+ if (sep == -1) {
+ return base;
+ }
+
+ return base + rs.substr(0, sep);
+}
+
+String String::get_file() const {
+ int sep = MAX(rfind("/"), rfind("\\"));
+ if (sep == -1) {
+ return *this;
+ }
+
+ return substr(sep + 1, length());
+}
+
+String String::get_extension() const {
+ int pos = rfind(".");
+ if (pos < 0 || pos < MAX(rfind("/"), rfind("\\"))) {
+ return "";
+ }
+
+ return substr(pos + 1, length());
+}
+
+String String::plus_file(const String &p_file) const {
+ if (is_empty()) {
+ return p_file;
+ }
+ if (operator[](length() - 1) == '/' || (p_file.size() > 0 && p_file.operator[](0) == '/')) {
+ return *this + p_file;
+ }
+ return *this + "/" + p_file;
+}
+
+String String::property_name_encode() const {
+ // Escape and quote strings with extended ASCII or further Unicode characters
+ // as well as '"', '=' or ' ' (32)
+ const char32_t *cstr = get_data();
+ for (int i = 0; cstr[i]; i++) {
+ if (cstr[i] == '=' || cstr[i] == '"' || cstr[i] < 33 || cstr[i] > 126) {
+ return "\"" + c_escape_multiline() + "\"";
+ }
+ }
+ // Keep as is
+ return *this;
+}
+
+// Changes made to the set of invalid characters must also be reflected in the String documentation.
+const String String::invalid_node_name_characters = ". : @ / \"";
+
+String String::validate_node_name() const {
+ Vector<String> chars = String::invalid_node_name_characters.split(" ");
+ String name = this->replace(chars[0], "");
+ for (int i = 1; i < chars.size(); i++) {
+ name = name.replace(chars[i], "");
+ }
+ return name;
+}
+
+String String::get_basename() const {
+ int pos = rfind(".");
+ if (pos < 0 || pos < MAX(rfind("/"), rfind("\\"))) {
+ return *this;
+ }
+
+ return substr(0, pos);
+}
+
+String itos(int64_t p_val) {
+ return String::num_int64(p_val);
+}
+
+String uitos(uint64_t p_val) {
+ return String::num_uint64(p_val);
+}
+
+String rtos(double p_val) {
+ return String::num(p_val);
+}
+
+String rtoss(double p_val) {
+ return String::num_scientific(p_val);
+}
+
+// Right-pad with a character.
+String String::rpad(int min_length, const String &character) const {
+ String s = *this;
+ int padding = min_length - s.length();
+ if (padding > 0) {
+ for (int i = 0; i < padding; i++) {
+ s = s + character;
+ }
+ }
+
+ return s;
+}
+
+// Left-pad with a character.
+String String::lpad(int min_length, const String &character) const {
+ String s = *this;
+ int padding = min_length - s.length();
+ if (padding > 0) {
+ for (int i = 0; i < padding; i++) {
+ s = character + s;
+ }
+ }
+
+ return s;
+}
+
+// sprintf is implemented in GDScript via:
+// "fish %s pie" % "frog"
+// "fish %s %d pie" % ["frog", 12]
+// In case of an error, the string returned is the error description and "error" is true.
+String String::sprintf(const Array &values, bool *error) const {
+ String formatted;
+ char32_t *self = (char32_t *)get_data();
+ bool in_format = false;
+ int value_index = 0;
+ int min_chars = 0;
+ int min_decimals = 0;
+ bool in_decimals = false;
+ bool pad_with_zeroes = false;
+ bool left_justified = false;
+ bool show_sign = false;
+
+ if (error) {
+ *error = true;
+ }
+
+ for (; *self; self++) {
+ const char32_t c = *self;
+
+ if (in_format) { // We have % - let's see what else we get.
+ switch (c) {
+ case '%': { // Replace %% with %
+ formatted += chr(c);
+ in_format = false;
+ break;
+ }
+ case 'd': // Integer (signed)
+ case 'o': // Octal
+ case 'x': // Hexadecimal (lowercase)
+ case 'X': { // Hexadecimal (uppercase)
+ if (value_index >= values.size()) {
+ return "not enough arguments for format string";
+ }
+
+ if (!values[value_index].is_num()) {
+ return "a number is required";
+ }
+
+ int64_t value = values[value_index];
+ int base = 16;
+ bool capitalize = false;
+ switch (c) {
+ case 'd':
+ base = 10;
+ break;
+ case 'o':
+ base = 8;
+ break;
+ case 'x':
+ break;
+ case 'X':
+ base = 16;
+ capitalize = true;
+ break;
+ }
+ // Get basic number.
+ String str = String::num_int64(ABS(value), base, capitalize);
+ int number_len = str.length();
+
+ // Padding.
+ int pad_chars_count = (value < 0 || show_sign) ? min_chars - 1 : min_chars;
+ String pad_char = pad_with_zeroes ? String("0") : String(" ");
+ if (left_justified) {
+ str = str.rpad(pad_chars_count, pad_char);
+ } else {
+ str = str.lpad(pad_chars_count, pad_char);
+ }
+
+ // Sign.
+ if (show_sign && value >= 0) {
+ str = str.insert(pad_with_zeroes ? 0 : str.length() - number_len, "+");
+ } else if (value < 0) {
+ str = str.insert(pad_with_zeroes ? 0 : str.length() - number_len, "-");
+ }
+
+ formatted += str;
+ ++value_index;
+ in_format = false;
+
+ break;
+ }
+ case 'f': { // Float
+ if (value_index >= values.size()) {
+ return "not enough arguments for format string";
+ }
+
+ if (!values[value_index].is_num()) {
+ return "a number is required";
+ }
+
+ double value = values[value_index];
+ bool is_negative = (value < 0);
+ String str = String::num(ABS(value), min_decimals);
+
+ // Pad decimals out.
+ str = str.pad_decimals(min_decimals);
+
+ int initial_len = str.length();
+
+ // Padding. Leave room for sign later if required.
+ int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars;
+ String pad_char = pad_with_zeroes ? String("0") : String(" ");
+ if (left_justified) {
+ if (pad_with_zeroes) {
+ return "left justification cannot be used with zeros as the padding";
+ } else {
+ str = str.rpad(pad_chars_count, pad_char);
+ }
+ } else {
+ str = str.lpad(pad_chars_count, pad_char);
+ }
+
+ // Add sign if needed.
+ if (show_sign || is_negative) {
+ String sign_char = is_negative ? "-" : "+";
+ if (left_justified) {
+ str = str.insert(0, sign_char);
+ } else {
+ str = str.insert(pad_with_zeroes ? 0 : str.length() - initial_len, sign_char);
+ }
+ }
+
+ formatted += str;
+ ++value_index;
+ in_format = false;
+ break;
+ }
+ case 's': { // String
+ if (value_index >= values.size()) {
+ return "not enough arguments for format string";
+ }
+
+ String str = values[value_index];
+ // Padding.
+ if (left_justified) {
+ str = str.rpad(min_chars);
+ } else {
+ str = str.lpad(min_chars);
+ }
+
+ formatted += str;
+ ++value_index;
+ in_format = false;
+ break;
+ }
+ case 'c': {
+ if (value_index >= values.size()) {
+ return "not enough arguments for format string";
+ }
+
+ // Convert to character.
+ String str;
+ if (values[value_index].is_num()) {
+ int value = values[value_index];
+ if (value < 0) {
+ return "unsigned integer is lower than minimum";
+ } else if (value >= 0xd800 && value <= 0xdfff) {
+ return "unsigned integer is invalid Unicode character";
+ } else if (value > 0x10ffff) {
+ return "unsigned integer is greater than maximum";
+ }
+ str = chr(values[value_index]);
+ } else if (values[value_index].get_type() == Variant::STRING) {
+ str = values[value_index];
+ if (str.length() != 1) {
+ return "%c requires number or single-character string";
+ }
+ } else {
+ return "%c requires number or single-character string";
+ }
+
+ // Padding.
+ if (left_justified) {
+ str = str.rpad(min_chars);
+ } else {
+ str = str.lpad(min_chars);
+ }
+
+ formatted += str;
+ ++value_index;
+ in_format = false;
+ break;
+ }
+ case '-': { // Left justify
+ left_justified = true;
+ break;
+ }
+ case '+': { // Show + if positive.
+ show_sign = true;
+ break;
+ }
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9': {
+ int n = c - '0';
+ if (in_decimals) {
+ min_decimals *= 10;
+ min_decimals += n;
+ } else {
+ if (c == '0' && min_chars == 0) {
+ pad_with_zeroes = true;
+ } else {
+ min_chars *= 10;
+ min_chars += n;
+ }
+ }
+ break;
+ }
+ case '.': { // Float separator.
+ if (in_decimals) {
+ return "too many decimal points in format";
+ }
+ in_decimals = true;
+ min_decimals = 0; // We want to add the value manually.
+ break;
+ }
+
+ case '*': { // Dynamic width, based on value.
+ if (value_index >= values.size()) {
+ return "not enough arguments for format string";
+ }
+
+ if (!values[value_index].is_num()) {
+ return "* wants number";
+ }
+
+ int size = values[value_index];
+
+ if (in_decimals) {
+ min_decimals = size;
+ } else {
+ min_chars = size;
+ }
+
+ ++value_index;
+ break;
+ }
+
+ default: {
+ return "unsupported format character";
+ }
+ }
+ } else { // Not in format string.
+ switch (c) {
+ case '%':
+ in_format = true;
+ // Back to defaults:
+ min_chars = 0;
+ min_decimals = 6;
+ pad_with_zeroes = false;
+ left_justified = false;
+ show_sign = false;
+ in_decimals = false;
+ break;
+ default:
+ formatted += chr(c);
+ }
+ }
+ }
+
+ if (in_format) {
+ return "incomplete format";
+ }
+
+ if (value_index != values.size()) {
+ return "not all arguments converted during string formatting";
+ }
+
+ if (error) {
+ *error = false;
+ }
+ return formatted;
+}
+
+String String::quote(String quotechar) const {
+ return quotechar + *this + quotechar;
+}
+
+String String::unquote() const {
+ if (!is_quoted()) {
+ return *this;
+ }
+
+ return substr(1, length() - 2);
+}
+
+Vector<uint8_t> String::to_ascii_buffer() const {
+ const String *s = this;
+ if (s->is_empty()) {
+ return Vector<uint8_t>();
+ }
+ CharString charstr = s->ascii();
+
+ Vector<uint8_t> retval;
+ size_t len = charstr.length();
+ retval.resize(len);
+ uint8_t *w = retval.ptrw();
+ memcpy(w, charstr.ptr(), len);
+
+ return retval;
+}
+
+Vector<uint8_t> String::to_utf8_buffer() const {
+ const String *s = this;
+ if (s->is_empty()) {
+ return Vector<uint8_t>();
+ }
+ CharString charstr = s->utf8();
+
+ Vector<uint8_t> retval;
+ size_t len = charstr.length();
+ retval.resize(len);
+ uint8_t *w = retval.ptrw();
+ memcpy(w, charstr.ptr(), len);
+
+ return retval;
+}
+
+Vector<uint8_t> String::to_utf16_buffer() const {
+ const String *s = this;
+ if (s->is_empty()) {
+ return Vector<uint8_t>();
+ }
+ Char16String charstr = s->utf16();
+
+ Vector<uint8_t> retval;
+ size_t len = charstr.length() * sizeof(char16_t);
+ retval.resize(len);
+ uint8_t *w = retval.ptrw();
+ memcpy(w, (const void *)charstr.ptr(), len);
+
+ return retval;
+}
+
+Vector<uint8_t> String::to_utf32_buffer() const {
+ const String *s = this;
+ if (s->is_empty()) {
+ return Vector<uint8_t>();
+ }
+
+ Vector<uint8_t> retval;
+ size_t len = s->length() * sizeof(char32_t);
+ retval.resize(len);
+ uint8_t *w = retval.ptrw();
+ memcpy(w, (const void *)s->ptr(), len);
+
+ return retval;
+}
+
+#ifdef TOOLS_ENABLED
+String TTR(const String &p_text, const String &p_context) {
+ if (TranslationServer::get_singleton()) {
+ return TranslationServer::get_singleton()->tool_translate(p_text, p_context);
+ }
+
+ return p_text;
+}
+
+String TTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
+ if (TranslationServer::get_singleton()) {
+ return TranslationServer::get_singleton()->tool_translate_plural(p_text, p_text_plural, p_n, p_context);
+ }
+
+ // Return message based on English plural rule if translation is not possible.
+ if (p_n == 1) {
+ return p_text;
+ }
+ return p_text_plural;
+}
+
+String DTR(const String &p_text, const String &p_context) {
+ // Comes straight from the XML, so remove indentation and any trailing whitespace.
+ const String text = p_text.dedent().strip_edges();
+
+ if (TranslationServer::get_singleton()) {
+ return TranslationServer::get_singleton()->doc_translate(text, p_context);
+ }
+
+ return text;
+}
+
+String DTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
+ const String text = p_text.dedent().strip_edges();
+ const String text_plural = p_text_plural.dedent().strip_edges();
+
+ if (TranslationServer::get_singleton()) {
+ return TranslationServer::get_singleton()->doc_translate_plural(text, text_plural, p_n, p_context);
+ }
+
+ // Return message based on English plural rule if translation is not possible.
+ if (p_n == 1) {
+ return text;
+ }
+ return text_plural;
+}
+#endif
+
+String RTR(const String &p_text, const String &p_context) {
+ if (TranslationServer::get_singleton()) {
+ String rtr = TranslationServer::get_singleton()->tool_translate(p_text, p_context);
+ if (rtr == String() || rtr == p_text) {
+ return TranslationServer::get_singleton()->translate(p_text, p_context);
+ } else {
+ return rtr;
+ }
+ }
+
+ return p_text;
+}
+
+String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
+ if (TranslationServer::get_singleton()) {
+ String rtr = TranslationServer::get_singleton()->tool_translate_plural(p_text, p_text_plural, p_n, p_context);
+ if (rtr == String() || rtr == p_text || rtr == p_text_plural) {
+ return TranslationServer::get_singleton()->translate_plural(p_text, p_text_plural, p_n, p_context);
+ } else {
+ return rtr;
+ }
+ }
+
+ // Return message based on English plural rule if translation is not possible.
+ if (p_n == 1) {
+ return p_text;
+ }
+ return p_text_plural;
+}
diff --git a/core/string/ustring.h b/core/string/ustring.h
new file mode 100644
index 0000000000..24da6b82af
--- /dev/null
+++ b/core/string/ustring.h
@@ -0,0 +1,561 @@
+/*************************************************************************/
+/* ustring.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef USTRING_GODOT_H
+#define USTRING_GODOT_H
+// Note: Renamed to avoid conflict with ICU header with the same name.
+
+#include "core/templates/cowdata.h"
+#include "core/templates/vector.h"
+#include "core/typedefs.h"
+#include "core/variant/array.h"
+
+/*************************************************************************/
+/* CharProxy */
+/*************************************************************************/
+
+template <class T>
+class CharProxy {
+ friend class Char16String;
+ friend class CharString;
+ friend class String;
+
+ const int _index;
+ CowData<T> &_cowdata;
+ static const T _null = 0;
+
+ _FORCE_INLINE_ CharProxy(const int &p_index, CowData<T> &cowdata) :
+ _index(p_index),
+ _cowdata(cowdata) {}
+
+public:
+ _FORCE_INLINE_ operator T() const {
+ if (unlikely(_index == _cowdata.size())) {
+ return _null;
+ }
+
+ return _cowdata.get(_index);
+ }
+
+ _FORCE_INLINE_ const T *operator&() const {
+ return _cowdata.ptr() + _index;
+ }
+
+ _FORCE_INLINE_ void operator=(const T &other) const {
+ _cowdata.set(_index, other);
+ }
+
+ _FORCE_INLINE_ void operator=(const CharProxy<T> &other) const {
+ _cowdata.set(_index, other.operator T());
+ }
+};
+
+/*************************************************************************/
+/* Char16String */
+/*************************************************************************/
+
+class Char16String {
+ CowData<char16_t> _cowdata;
+ static const char16_t _null;
+
+public:
+ _FORCE_INLINE_ char16_t *ptrw() { return _cowdata.ptrw(); }
+ _FORCE_INLINE_ const char16_t *ptr() const { return _cowdata.ptr(); }
+ _FORCE_INLINE_ int size() const { return _cowdata.size(); }
+ Error resize(int p_size) { return _cowdata.resize(p_size); }
+
+ _FORCE_INLINE_ char16_t get(int p_index) const { return _cowdata.get(p_index); }
+ _FORCE_INLINE_ void set(int p_index, const char16_t &p_elem) { _cowdata.set(p_index, p_elem); }
+ _FORCE_INLINE_ const char16_t &operator[](int p_index) const {
+ if (unlikely(p_index == _cowdata.size())) {
+ return _null;
+ }
+
+ return _cowdata.get(p_index);
+ }
+ _FORCE_INLINE_ CharProxy<char16_t> operator[](int p_index) { return CharProxy<char16_t>(p_index, _cowdata); }
+
+ _FORCE_INLINE_ Char16String() {}
+ _FORCE_INLINE_ Char16String(const Char16String &p_str) { _cowdata._ref(p_str._cowdata); }
+ _FORCE_INLINE_ Char16String &operator=(const Char16String &p_str) {
+ _cowdata._ref(p_str._cowdata);
+ return *this;
+ }
+ _FORCE_INLINE_ Char16String(const char16_t *p_cstr) { copy_from(p_cstr); }
+
+ Char16String &operator=(const char16_t *p_cstr);
+ bool operator<(const Char16String &p_right) const;
+ Char16String &operator+=(char16_t p_char);
+ int length() const { return size() ? size() - 1 : 0; }
+ const char16_t *get_data() const;
+ operator const char16_t *() const { return get_data(); };
+
+protected:
+ void copy_from(const char16_t *p_cstr);
+};
+
+/*************************************************************************/
+/* CharString */
+/*************************************************************************/
+
+class CharString {
+ CowData<char> _cowdata;
+ static const char _null;
+
+public:
+ _FORCE_INLINE_ char *ptrw() { return _cowdata.ptrw(); }
+ _FORCE_INLINE_ const char *ptr() const { return _cowdata.ptr(); }
+ _FORCE_INLINE_ int size() const { return _cowdata.size(); }
+ Error resize(int p_size) { return _cowdata.resize(p_size); }
+
+ _FORCE_INLINE_ char get(int p_index) const { return _cowdata.get(p_index); }
+ _FORCE_INLINE_ void set(int p_index, const char &p_elem) { _cowdata.set(p_index, p_elem); }
+ _FORCE_INLINE_ const char &operator[](int p_index) const {
+ if (unlikely(p_index == _cowdata.size())) {
+ return _null;
+ }
+
+ return _cowdata.get(p_index);
+ }
+ _FORCE_INLINE_ CharProxy<char> operator[](int p_index) { return CharProxy<char>(p_index, _cowdata); }
+
+ _FORCE_INLINE_ CharString() {}
+ _FORCE_INLINE_ CharString(const CharString &p_str) { _cowdata._ref(p_str._cowdata); }
+ _FORCE_INLINE_ CharString &operator=(const CharString &p_str) {
+ _cowdata._ref(p_str._cowdata);
+ return *this;
+ }
+ _FORCE_INLINE_ CharString(const char *p_cstr) { copy_from(p_cstr); }
+
+ CharString &operator=(const char *p_cstr);
+ bool operator<(const CharString &p_right) const;
+ CharString &operator+=(char p_char);
+ int length() const { return size() ? size() - 1 : 0; }
+ const char *get_data() const;
+ operator const char *() const { return get_data(); };
+
+protected:
+ void copy_from(const char *p_cstr);
+};
+
+/*************************************************************************/
+/* String */
+/*************************************************************************/
+
+struct StrRange {
+ const char32_t *c_str;
+ int len;
+
+ StrRange(const char32_t *p_c_str = nullptr, int p_len = 0) {
+ c_str = p_c_str;
+ len = p_len;
+ }
+};
+
+class String {
+ CowData<char32_t> _cowdata;
+ static const char32_t _null;
+
+ void copy_from(const char *p_cstr);
+ void copy_from(const char *p_cstr, const int p_clip_to);
+ void copy_from(const wchar_t *p_cstr);
+ void copy_from(const wchar_t *p_cstr, const int p_clip_to);
+ void copy_from(const char32_t *p_cstr);
+ void copy_from(const char32_t *p_cstr, const int p_clip_to);
+
+ void copy_from(const char32_t &p_char);
+
+ void copy_from_unchecked(const char32_t *p_char, const int p_length);
+
+ bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const;
+ int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const;
+
+public:
+ enum {
+ npos = -1 ///<for "some" compatibility with std::string (npos is a huge value in std::string)
+ };
+
+ _FORCE_INLINE_ char32_t *ptrw() { return _cowdata.ptrw(); }
+ _FORCE_INLINE_ const char32_t *ptr() const { return _cowdata.ptr(); }
+
+ void remove(int p_index) { _cowdata.remove(p_index); }
+
+ _FORCE_INLINE_ void clear() { resize(0); }
+
+ _FORCE_INLINE_ char32_t get(int p_index) const { return _cowdata.get(p_index); }
+ _FORCE_INLINE_ void set(int p_index, const char32_t &p_elem) { _cowdata.set(p_index, p_elem); }
+ _FORCE_INLINE_ int size() const { return _cowdata.size(); }
+ Error resize(int p_size) { return _cowdata.resize(p_size); }
+
+ _FORCE_INLINE_ const char32_t &operator[](int p_index) const {
+ if (unlikely(p_index == _cowdata.size())) {
+ return _null;
+ }
+
+ return _cowdata.get(p_index);
+ }
+ _FORCE_INLINE_ CharProxy<char32_t> operator[](int p_index) { return CharProxy<char32_t>(p_index, _cowdata); }
+
+ bool operator==(const String &p_str) const;
+ bool operator!=(const String &p_str) const;
+ String operator+(const String &p_str) const;
+
+ String &operator+=(const String &);
+ String &operator+=(char32_t p_char);
+ String &operator+=(const char *p_str);
+ String &operator+=(const wchar_t *p_str);
+ String &operator+=(const char32_t *p_str);
+
+ /* Compatibility Operators */
+
+ void operator=(const char *p_str);
+ void operator=(const wchar_t *p_str);
+ void operator=(const char32_t *p_str);
+
+ bool operator==(const char *p_str) const;
+ bool operator==(const wchar_t *p_str) const;
+ bool operator==(const char32_t *p_str) const;
+ bool operator==(const StrRange &p_str_range) const;
+
+ bool operator!=(const char *p_str) const;
+ bool operator!=(const wchar_t *p_str) const;
+ bool operator!=(const char32_t *p_str) const;
+
+ bool operator<(const char32_t *p_str) const;
+ bool operator<(const char *p_str) const;
+ bool operator<(const wchar_t *p_str) const;
+
+ bool operator<(const String &p_str) const;
+ bool operator<=(const String &p_str) const;
+ bool operator>(const String &p_str) const;
+ bool operator>=(const String &p_str) const;
+
+ signed char casecmp_to(const String &p_str) const;
+ signed char nocasecmp_to(const String &p_str) const;
+ signed char naturalnocasecmp_to(const String &p_str) const;
+
+ const char32_t *get_data() const;
+ /* standard size stuff */
+
+ _FORCE_INLINE_ int length() const {
+ int s = size();
+ return s ? (s - 1) : 0; // length does not include zero
+ }
+
+ bool is_valid_string() const;
+
+ /* complex helpers */
+ String substr(int p_from, int p_chars = -1) const;
+ int find(const String &p_str, int p_from = 0) const; ///< return <0 if failed
+ int find(const char *p_str, int p_from = 0) const; ///< return <0 if failed
+ int find_char(const char32_t &p_char, int p_from = 0) const; ///< return <0 if failed
+ int findn(const String &p_str, int p_from = 0) const; ///< return <0 if failed, case insensitive
+ int rfind(const String &p_str, int p_from = -1) const; ///< return <0 if failed
+ int rfindn(const String &p_str, int p_from = -1) const; ///< return <0 if failed, case insensitive
+ int findmk(const Vector<String> &p_keys, int p_from = 0, int *r_key = nullptr) const; ///< return <0 if failed
+ bool match(const String &p_wildcard) const;
+ bool matchn(const String &p_wildcard) const;
+ bool begins_with(const String &p_string) const;
+ bool begins_with(const char *p_string) const;
+ bool ends_with(const String &p_string) const;
+ bool is_enclosed_in(const String &p_string) const;
+ bool is_subsequence_of(const String &p_string) const;
+ bool is_subsequence_ofi(const String &p_string) const;
+ bool is_quoted() const;
+ Vector<String> bigrams() const;
+ float similarity(const String &p_string) const;
+ String format(const Variant &values, String placeholder = "{_}") const;
+ String replace_first(const String &p_key, const String &p_with) const;
+ String replace(const String &p_key, const String &p_with) const;
+ String replace(const char *p_key, const char *p_with) const;
+ String replacen(const String &p_key, const String &p_with) const;
+ String repeat(int p_count) const;
+ String insert(int p_at_pos, const String &p_string) const;
+ String pad_decimals(int p_digits) const;
+ String pad_zeros(int p_digits) const;
+ String trim_prefix(const String &p_prefix) const;
+ String trim_suffix(const String &p_suffix) const;
+ String lpad(int min_length, const String &character = " ") const;
+ String rpad(int min_length, const String &character = " ") const;
+ String sprintf(const Array &values, bool *error) const;
+ String quote(String quotechar = "\"") const;
+ String unquote() const;
+ static String num(double p_num, int p_decimals = -1);
+ static String num_scientific(double p_num);
+ static String num_real(double p_num, bool p_trailing = true);
+ static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false);
+ static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false);
+ static String chr(char32_t p_char);
+ static String md5(const uint8_t *p_md5);
+ static String hex_encode_buffer(const uint8_t *p_buffer, int p_len);
+ bool is_numeric() const;
+
+ double to_float() const;
+ int64_t hex_to_int() const;
+ int64_t bin_to_int() const;
+ int64_t to_int() const;
+
+ static int64_t to_int(const char *p_str, int p_len = -1);
+ static int64_t to_int(const wchar_t *p_str, int p_len = -1);
+ static int64_t to_int(const char32_t *p_str, int p_len = -1, bool p_clamp = false);
+
+ static double to_float(const char *p_str);
+ static double to_float(const wchar_t *p_str, const wchar_t **r_end = nullptr);
+ static double to_float(const char32_t *p_str, const char32_t **r_end = nullptr);
+
+ String capitalize() const;
+ String camelcase_to_underscore(bool lowercase = true) const;
+
+ String get_with_code_lines() const;
+ int get_slice_count(String p_splitter) const;
+ String get_slice(String p_splitter, int p_slice) const;
+ String get_slicec(char32_t p_splitter, int p_slice) const;
+
+ Vector<String> split(const String &p_splitter, bool p_allow_empty = true, int p_maxsplit = 0) const;
+ Vector<String> rsplit(const String &p_splitter, bool p_allow_empty = true, int p_maxsplit = 0) const;
+ Vector<String> split_spaces() const;
+ Vector<float> split_floats(const String &p_splitter, bool p_allow_empty = true) const;
+ Vector<float> split_floats_mk(const Vector<String> &p_splitters, bool p_allow_empty = true) const;
+ Vector<int> split_ints(const String &p_splitter, bool p_allow_empty = true) const;
+ Vector<int> split_ints_mk(const Vector<String> &p_splitters, bool p_allow_empty = true) const;
+
+ String join(Vector<String> parts) const;
+
+ static char32_t char_uppercase(char32_t p_char);
+ static char32_t char_lowercase(char32_t p_char);
+ String to_upper() const;
+ String to_lower() const;
+
+ int count(const String &p_string, int p_from = 0, int p_to = 0) const;
+ int countn(const String &p_string, int p_from = 0, int p_to = 0) const;
+
+ String left(int p_pos) const;
+ String right(int p_pos) const;
+ String dedent() const;
+ String strip_edges(bool left = true, bool right = true) const;
+ String strip_escapes() const;
+ String lstrip(const String &p_chars) const;
+ String rstrip(const String &p_chars) const;
+ String get_extension() const;
+ String get_basename() const;
+ String plus_file(const String &p_file) const;
+ char32_t unicode_at(int p_idx) const;
+
+ void erase(int p_pos, int p_chars);
+
+ CharString ascii(bool p_allow_extended = false) const;
+ CharString utf8() const;
+ bool parse_utf8(const char *p_utf8, int p_len = -1); //return true on error
+ static String utf8(const char *p_utf8, int p_len = -1);
+
+ Char16String utf16() const;
+ bool parse_utf16(const char16_t *p_utf16, int p_len = -1); //return true on error
+ static String utf16(const char16_t *p_utf16, int p_len = -1);
+
+ static uint32_t hash(const char32_t *p_cstr, int p_len); /* hash the string */
+ static uint32_t hash(const char32_t *p_cstr); /* hash the string */
+ static uint32_t hash(const wchar_t *p_cstr, int p_len); /* hash the string */
+ static uint32_t hash(const wchar_t *p_cstr); /* hash the string */
+ static uint32_t hash(const char *p_cstr, int p_len); /* hash the string */
+ static uint32_t hash(const char *p_cstr); /* hash the string */
+ uint32_t hash() const; /* hash the string */
+ uint64_t hash64() const; /* hash the string */
+ String md5_text() const;
+ String sha1_text() const;
+ String sha256_text() const;
+ Vector<uint8_t> md5_buffer() const;
+ Vector<uint8_t> sha1_buffer() const;
+ Vector<uint8_t> sha256_buffer() const;
+
+ _FORCE_INLINE_ bool is_empty() const { return length() == 0; }
+
+ // path functions
+ bool is_absolute_path() const;
+ bool is_relative_path() const;
+ bool is_resource_file() const;
+ String path_to(const String &p_path) const;
+ String path_to_file(const String &p_path) const;
+ String get_base_dir() const;
+ String get_file() const;
+ static String humanize_size(uint64_t p_size);
+ String simplify_path() const;
+
+ String xml_escape(bool p_escape_quotes = false) const;
+ String xml_unescape() const;
+ String uri_encode() const;
+ String uri_decode() const;
+ String c_escape() const;
+ String c_escape_multiline() const;
+ String c_unescape() const;
+ String json_escape() const;
+ String word_wrap(int p_chars_per_line) const;
+ Error parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path) const;
+
+ String property_name_encode() const;
+
+ // node functions
+ static const String invalid_node_name_characters;
+ String validate_node_name() const;
+
+ bool is_valid_identifier() const;
+ bool is_valid_int() const;
+ bool is_valid_float() const;
+ bool is_valid_hex_number(bool p_with_prefix) const;
+ bool is_valid_html_color() const;
+ bool is_valid_ip_address() const;
+ bool is_valid_filename() const;
+
+ /**
+ * The constructors must not depend on other overloads
+ */
+
+ _FORCE_INLINE_ String() {}
+ _FORCE_INLINE_ String(const String &p_str) { _cowdata._ref(p_str._cowdata); }
+
+ String &operator=(const String &p_str) {
+ _cowdata._ref(p_str._cowdata);
+ return *this;
+ }
+
+ Vector<uint8_t> to_ascii_buffer() const;
+ Vector<uint8_t> to_utf8_buffer() const;
+ Vector<uint8_t> to_utf16_buffer() const;
+ Vector<uint8_t> to_utf32_buffer() const;
+
+ String(const char *p_str);
+ String(const wchar_t *p_str);
+ String(const char32_t *p_str);
+ String(const char *p_str, int p_clip_to_len);
+ String(const wchar_t *p_str, int p_clip_to_len);
+ String(const char32_t *p_str, int p_clip_to_len);
+ String(const StrRange &p_range);
+};
+
+bool operator==(const char *p_chr, const String &p_str);
+bool operator==(const wchar_t *p_chr, const String &p_str);
+bool operator!=(const char *p_chr, const String &p_str);
+bool operator!=(const wchar_t *p_chr, const String &p_str);
+
+String operator+(const char *p_chr, const String &p_str);
+String operator+(const wchar_t *p_chr, const String &p_str);
+String operator+(char32_t p_chr, const String &p_str);
+
+String itos(int64_t p_val);
+String uitos(uint64_t p_val);
+String rtos(double p_val);
+String rtoss(double p_val); //scientific version
+
+struct NoCaseComparator {
+ bool operator()(const String &p_a, const String &p_b) const {
+ return p_a.nocasecmp_to(p_b) < 0;
+ }
+};
+
+struct NaturalNoCaseComparator {
+ bool operator()(const String &p_a, const String &p_b) const {
+ return p_a.naturalnocasecmp_to(p_b) < 0;
+ }
+};
+
+template <typename L, typename R>
+_FORCE_INLINE_ bool is_str_less(const L *l_ptr, const R *r_ptr) {
+ while (true) {
+ const char32_t l = *l_ptr;
+ const char32_t r = *r_ptr;
+
+ if (l == 0 && r == 0) {
+ return false;
+ } else if (l == 0) {
+ return true;
+ } else if (r == 0) {
+ return false;
+ } else if (l < r) {
+ return true;
+ } else if (l > r) {
+ return false;
+ }
+
+ l_ptr++;
+ r_ptr++;
+ }
+}
+
+/* end of namespace */
+
+// Tool translate (TTR and variants) for the editor UI,
+// and doc translate for the class reference (DTR).
+#ifdef TOOLS_ENABLED
+// Gets parsed.
+String TTR(const String &p_text, const String &p_context = "");
+String TTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
+String DTR(const String &p_text, const String &p_context = "");
+String DTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
+// Use for C strings.
+#define TTRC(m_value) (m_value)
+// Use to avoid parsing (for use later with C strings).
+#define TTRGET(m_value) TTR(m_value)
+
+#else
+#define TTR(m_value) String()
+#define TTRN(m_value) String()
+#define DTR(m_value) String()
+#define DTRN(m_value) String()
+#define TTRC(m_value) (m_value)
+#define TTRGET(m_value) (m_value)
+#endif
+
+// Runtime translate for the public node API.
+String RTR(const String &p_text, const String &p_context = "");
+String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
+
+bool is_symbol(char32_t c);
+bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end);
+
+_FORCE_INLINE_ void sarray_add_str(Vector<String> &arr) {
+}
+
+_FORCE_INLINE_ void sarray_add_str(Vector<String> &arr, const String &p_str) {
+ arr.push_back(p_str);
+}
+
+template <class... P>
+_FORCE_INLINE_ void sarray_add_str(Vector<String> &arr, const String &p_str, P... p_args) {
+ arr.push_back(p_str);
+ sarray_add_str(arr, p_args...);
+}
+
+template <class... P>
+_FORCE_INLINE_ Vector<String> sarray(P... p_args) {
+ Vector<String> arr;
+ sarray_add_str(arr, p_args...);
+ return arr;
+}
+
+#endif // USTRING_GODOT_H