/*************************************************************************/ /* editor_font_import_plugin.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2017 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 "editor_font_import_plugin.h" #include "editor/editor_file_dialog.h" #include "editor/editor_node.h" #include "editor_atlas.h" #include "io/image_loader.h" #include "io/resource_saver.h" #include "os/file_access.h" #include "scene/gui/dialogs.h" #ifdef FREETYPE_ENABLED #include #include FT_FREETYPE_H #endif class _EditorFontImportOptions : public Object { OBJ_TYPE(_EditorFontImportOptions, Object); public: enum FontMode { FONT_BITMAP, FONT_DISTANCE_FIELD }; enum ColorType { COLOR_WHITE, COLOR_CUSTOM, COLOR_GRADIENT_RANGE, COLOR_GRADIENT_IMAGE }; int char_extra_spacing; int top_extra_spacing; int bottom_extra_spacing; int space_extra_spacing; enum CharacterSet { CHARSET_ASCII, CHARSET_LATIN, CHARSET_UNICODE, CHARSET_CUSTOM, CHARSET_CUSTOM_LATIN }; FontMode font_mode; CharacterSet character_set; String custom_file; bool shadow; Vector2 shadow_offset; int shadow_radius; Color shadow_color; float shadow_transition; bool shadow2; Vector2 shadow2_offset; int shadow2_radius; Color shadow2_color; float shadow2_transition; ColorType color_type; Color color; Color gradient_begin; Color gradient_end; bool color_use_monochrome; String gradient_image; bool disable_filter; bool round_advance; bool premultiply_alpha; bool _set(const StringName &p_name, const Variant &p_value) { String n = p_name; if (n == "mode/mode") { font_mode = FontMode(int(p_value)); _change_notify(); } else if (n == "extra_space/char") char_extra_spacing = p_value; else if (n == "extra_space/space") space_extra_spacing = p_value; else if (n == "extra_space/top") top_extra_spacing = p_value; else if (n == "extra_space/bottom") bottom_extra_spacing = p_value; else if (n == "character_set/mode") { character_set = CharacterSet(int(p_value)); _change_notify(); } else if (n == "character_set/custom") custom_file = p_value; else if (n == "shadow/enabled") { shadow = p_value; _change_notify(); } else if (n == "shadow/radius") shadow_radius = p_value; else if (n == "shadow/offset") shadow_offset = p_value; else if (n == "shadow/color") shadow_color = p_value; else if (n == "shadow/transition") shadow_transition = p_value; else if (n == "shadow2/enabled") { shadow2 = p_value; _change_notify(); } else if (n == "shadow2/radius") shadow2_radius = p_value; else if (n == "shadow2/offset") shadow2_offset = p_value; else if (n == "shadow2/color") shadow2_color = p_value; else if (n == "shadow2/transition") shadow2_transition = p_value; else if (n == "color/mode") { color_type = ColorType(int(p_value)); _change_notify(); } else if (n == "color/color") color = p_value; else if (n == "color/begin") gradient_begin = p_value; else if (n == "color/end") gradient_end = p_value; else if (n == "color/image") gradient_image = p_value; else if (n == "color/monochrome") color_use_monochrome = p_value; else if (n == "advanced/round_advance") round_advance = p_value; else if (n == "advanced/disable_filter") disable_filter = p_value; else if (n == "advanced/premultiply_alpha") premultiply_alpha = p_value; else return false; emit_signal("changed"); return true; } bool _get(const StringName &p_name, Variant &r_ret) const { String n = p_name; if (n == "mode/mode") r_ret = font_mode; else if (n == "extra_space/char") r_ret = char_extra_spacing; else if (n == "extra_space/space") r_ret = space_extra_spacing; else if (n == "extra_space/top") r_ret = top_extra_spacing; else if (n == "extra_space/bottom") r_ret = bottom_extra_spacing; else if (n == "character_set/mode") r_ret = character_set; else if (n == "character_set/custom") r_ret = custom_file; else if (n == "shadow/enabled") r_ret = shadow; else if (n == "shadow/radius") r_ret = shadow_radius; else if (n == "shadow/offset") r_ret = shadow_offset; else if (n == "shadow/color") r_ret = shadow_color; else if (n == "shadow/transition") r_ret = shadow_transition; else if (n == "shadow2/enabled") r_ret = shadow2; else if (n == "shadow2/radius") r_ret = shadow2_radius; else if (n == "shadow2/offset") r_ret = shadow2_offset; else if (n == "shadow2/color") r_ret = shadow2_color; else if (n == "shadow2/transition") r_ret = shadow2_transition; else if (n == "color/mode") r_ret = color_type; else if (n == "color/color") r_ret = color; else if (n == "color/begin") r_ret = gradient_begin; else if (n == "color/end") r_ret = gradient_end; else if (n == "color/image") r_ret = gradient_image; else if (n == "color/monochrome") r_ret = color_use_monochrome; else if (n == "advanced/round_advance") r_ret = round_advance; else if (n == "advanced/disable_filter") r_ret = disable_filter; else if (n == "advanced/premultiply_alpha") r_ret = premultiply_alpha; else return false; return true; } void _get_property_list(List *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, "mode/mode", PROPERTY_HINT_ENUM, "Bitmap,Distance Field")); p_list->push_back(PropertyInfo(Variant::INT, "extra_space/char", PROPERTY_HINT_RANGE, "-64,64,1")); p_list->push_back(PropertyInfo(Variant::INT, "extra_space/space", PROPERTY_HINT_RANGE, "-64,64,1")); p_list->push_back(PropertyInfo(Variant::INT, "extra_space/top", PROPERTY_HINT_RANGE, "-64,64,1")); p_list->push_back(PropertyInfo(Variant::INT, "extra_space/bottom", PROPERTY_HINT_RANGE, "-64,64,1")); p_list->push_back(PropertyInfo(Variant::INT, "character_set/mode", PROPERTY_HINT_ENUM, "Ascii,Latin,Unicode,Custom,Custom&Latin")); if (character_set >= CHARSET_CUSTOM) p_list->push_back(PropertyInfo(Variant::STRING, "character_set/custom", PROPERTY_HINT_GLOBAL_FILE)); int usage = PROPERTY_USAGE_DEFAULT; if (font_mode == FONT_DISTANCE_FIELD) { usage = PROPERTY_USAGE_NOEDITOR; } { p_list->push_back(PropertyInfo(Variant::BOOL, "shadow/enabled", PROPERTY_HINT_NONE, "", usage)); if (shadow) { p_list->push_back(PropertyInfo(Variant::INT, "shadow/radius", PROPERTY_HINT_RANGE, "-64,64,1", usage)); p_list->push_back(PropertyInfo(Variant::VECTOR2, "shadow/offset", PROPERTY_HINT_NONE, "", usage)); p_list->push_back(PropertyInfo(Variant::COLOR, "shadow/color", PROPERTY_HINT_NONE, "", usage)); p_list->push_back(PropertyInfo(Variant::REAL, "shadow/transition", PROPERTY_HINT_EXP_EASING, "", usage)); } p_list->push_back(PropertyInfo(Variant::BOOL, "shadow2/enabled", PROPERTY_HINT_NONE, "", usage)); if (shadow2) { p_list->push_back(PropertyInfo(Variant::INT, "shadow2/radius", PROPERTY_HINT_RANGE, "-64,64,1", usage)); p_list->push_back(PropertyInfo(Variant::VECTOR2, "shadow2/offset", PROPERTY_HINT_NONE, "", usage)); p_list->push_back(PropertyInfo(Variant::COLOR, "shadow2/color", PROPERTY_HINT_NONE, "", usage)); p_list->push_back(PropertyInfo(Variant::REAL, "shadow2/transition", PROPERTY_HINT_EXP_EASING, "", usage)); } p_list->push_back(PropertyInfo(Variant::INT, "color/mode", PROPERTY_HINT_ENUM, "White,Color,Gradient,Gradient Image", usage)); if (color_type == COLOR_CUSTOM) { p_list->push_back(PropertyInfo(Variant::COLOR, "color/color", PROPERTY_HINT_NONE, "", usage)); } if (color_type == COLOR_GRADIENT_RANGE) { p_list->push_back(PropertyInfo(Variant::COLOR, "color/begin", PROPERTY_HINT_NONE, "", usage)); p_list->push_back(PropertyInfo(Variant::COLOR, "color/end", PROPERTY_HINT_NONE, "", usage)); } if (color_type == COLOR_GRADIENT_IMAGE) { p_list->push_back(PropertyInfo(Variant::STRING, "color/image", PROPERTY_HINT_GLOBAL_FILE, "", usage)); } p_list->push_back(PropertyInfo(Variant::BOOL, "color/monochrome", PROPERTY_HINT_NONE, "", usage)); } p_list->push_back(PropertyInfo(Variant::BOOL, "advanced/round_advance")); p_list->push_back(PropertyInfo(Variant::BOOL, "advanced/disable_filter")); p_list->push_back(PropertyInfo(Variant::BOOL, "advanced/premultiply_alpha")); } static void _bind_methods() { ADD_SIGNAL(MethodInfo("changed")); } void reset() { char_extra_spacing = 0; top_extra_spacing = 0; bottom_extra_spacing = 0; space_extra_spacing = 0; character_set = CHARSET_LATIN; shadow = false; shadow_radius = 2; shadow_color = Color(0, 0, 0, 0.3); shadow_transition = 1.0; shadow2 = false; shadow2_radius = 2; shadow2_color = Color(0, 0, 0, 0.3); shadow2_transition = 1.0; color_type = COLOR_WHITE; color = Color(1, 1, 1, 1); gradient_begin = Color(1, 1, 1, 1); gradient_end = Color(0.5, 0.5, 0.5, 1); color_use_monochrome = false; font_mode = FONT_BITMAP; round_advance = true; disable_filter = false; premultiply_alpha = false; } _EditorFontImportOptions() { font_mode = FONT_BITMAP; char_extra_spacing = 0; top_extra_spacing = 0; bottom_extra_spacing = 0; space_extra_spacing = 0; character_set = CHARSET_LATIN; shadow = false; shadow_radius = 2; shadow_color = Color(0, 0, 0, 0.3); shadow_transition = 1.0; shadow2 = false; shadow2_radius = 2; shadow2_color = Color(0, 0, 0, 0.3); shadow2_transition = 1.0; color_type = COLOR_WHITE; color = Color(1, 1, 1, 1); gradient_begin = Color(1, 1, 1, 1); gradient_end = Color(0.5, 0.5, 0.5, 1); color_use_monochrome = false; round_advance = true; disable_filter = false; premultiply_alpha = false; } }; class EditorFontImportDialog : public ConfirmationDialog { OBJ_TYPE(EditorFontImportDialog, ConfirmationDialog); EditorLineEditFileChooser *source; EditorLineEditFileChooser *dest; SpinBox *font_size; LineEdit *test_string; ColorPickerButton *test_color; Label *test_label; PropertyEditor *prop_edit; Timer *timer; ConfirmationDialog *error_dialog; Ref get_rimd() { Ref imd = memnew(ResourceImportMetadata); List pl; options->_get_property_list(&pl); for (List::Element *E = pl.front(); E; E = E->next()) { Variant v; String opt = E->get().name; options->_get(opt, v); if (opt == "color/image" || opt == "character_set/custom") { v = EditorImportPlugin::validate_source_path(v); } imd->set_option(opt, v); } String src_path = EditorImportPlugin::validate_source_path(source->get_line_edit()->get_text()); //print_line("pre src path "+source->get_line_edit()->get_text()); //print_line("src path "+src_path); imd->add_source(src_path); imd->set_option("font/size", font_size->get_val()); return imd; } void _src_changed(String) { _prop_changed(); } void _update_text2(String) { _update_text(); } void _update_text3(Color) { _update_text(); } void _update_text() { test_label->set_text(""); test_label->set_text(test_string->get_text()); test_label->add_color_override("font_color", test_color->get_color()); } void _update() { Ref imd = get_rimd(); Ref font = plugin->generate_font(imd); test_label->add_font_override("font", font); _update_text(); } void _font_size_changed(double) { _prop_changed(); } void _prop_changed() { timer->start(); } void _import_inc(String p_font) { Ref font = ResourceLoader::load(p_font); if (!font.is_valid()) return; Ref tex = font->get_texture(0); if (tex.is_null()) return; FileAccessRef f = FileAccess::open(p_font.basename() + ".inc", FileAccess::WRITE); Vector ck = font->get_char_keys(); f->store_line("static const int _builtin_font_height=" + itos(font->get_height()) + ";"); f->store_line("static const int _builtin_font_ascent=" + itos(font->get_ascent()) + ";"); f->store_line("static const int _builtin_font_charcount=" + itos(ck.size()) + ";"); f->store_line("static const int _builtin_font_charrects[" + itos(ck.size()) + "][8]={"); f->store_line("/* charidx , ofs_x, ofs_y, size_x, size_y, valign, halign, advance */"); for (int i = 0; i < ck.size(); i++) { CharType k = ck[i]; BitmapFont::Character c = font->get_character(k); f->store_line("{" + itos(k) + "," + rtos(c.rect.pos.x) + "," + rtos(c.rect.pos.y) + "," + rtos(c.rect.size.x) + "," + rtos(c.rect.size.y) + "," + rtos(c.v_align) + "," + rtos(c.h_align) + "," + rtos(c.advance) + "},"); } f->store_line("};"); Vector kp = font->get_kerning_pair_keys(); f->store_line("static const int _builtin_font_kerning_pair_count=" + itos(kp.size()) + ";"); f->store_line("static const int _builtin_font_kerning_pairs[" + itos(kp.size()) + "][3]={"); for (int i = 0; i < kp.size(); i++) { int d = font->get_kerning_pair(kp[i].A, kp[i].B); f->store_line("{" + itos(kp[i].A) + "," + itos(kp[i].B) + "," + itos(d) + "},"); } f->store_line("};"); Image img = tex->get_data(); f->store_line("static const int _builtin_font_img_width=" + itos(img.get_width()) + ";"); f->store_line("static const int _builtin_font_img_height=" + itos(img.get_height()) + ";"); String fname = p_font.basename() + ".sv.png"; ResourceSaver::save(fname, tex); Vector data = FileAccess::get_file_as_array(fname); f->store_line("static const int _builtin_font_img_data_size=" + itos(data.size()) + ";"); f->store_line("static const unsigned char _builtin_font_img_data[" + itos(data.size()) + "]={"); for (int i = 0; i < data.size(); i++) { f->store_line(itos(data[i]) + ","); } f->store_line("};"); } void _import() { if (source->get_line_edit()->get_text() == "") { error_dialog->set_text(TTR("No source font file!")); error_dialog->popup_centered(Size2(200, 100) * EDSCALE); return; } if (dest->get_line_edit()->get_text() == "") { error_dialog->set_text(TTR("No target font resource!")); error_dialog->popup_centered(Size2(200, 100) * EDSCALE); return; } if (dest->get_line_edit()->get_text().get_file() == ".fnt") { dest->get_line_edit()->set_text(dest->get_line_edit()->get_text().get_base_dir() + "/" + source->get_line_edit()->get_text().get_file().basename() + ".fnt"); } if (dest->get_line_edit()->get_text().extension() == dest->get_line_edit()->get_text()) { dest->get_line_edit()->set_text(dest->get_line_edit()->get_text() + ".fnt"); } if (dest->get_line_edit()->get_text().extension().to_lower() != "fnt") { error_dialog->set_text(TTR("Invalid file extension.\nPlease use .fnt.")); error_dialog->popup_centered(Size2(200, 100)); return; } Ref rimd = get_rimd(); if (rimd.is_null()) { error_dialog->set_text(TTR("Can't load/process source font.")); error_dialog->popup_centered(Size2(200, 100) * EDSCALE); return; } Error err = plugin->import(dest->get_line_edit()->get_text(), rimd); if (err != OK) { error_dialog->set_text(TTR("Couldn't save font.")); error_dialog->popup_centered(Size2(200, 100) * EDSCALE); return; } _import_inc(dest->get_line_edit()->get_text()); hide(); } EditorFontImportPlugin *plugin; _EditorFontImportOptions *options; static void _bind_methods() { ObjectTypeDB::bind_method("_update", &EditorFontImportDialog::_update); ObjectTypeDB::bind_method("_update_text", &EditorFontImportDialog::_update_text); ObjectTypeDB::bind_method("_update_text2", &EditorFontImportDialog::_update_text2); ObjectTypeDB::bind_method("_update_text3", &EditorFontImportDialog::_update_text3); ObjectTypeDB::bind_method("_prop_changed", &EditorFontImportDialog::_prop_changed); ObjectTypeDB::bind_method("_src_changed", &EditorFontImportDialog::_src_changed); ObjectTypeDB::bind_method("_font_size_changed", &EditorFontImportDialog::_font_size_changed); ObjectTypeDB::bind_method("_import", &EditorFontImportDialog::_import); } public: void _notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { prop_edit->edit(options); _update_text(); } } void popup_import(const String &p_path) { popup_centered(Size2(600, 500) * EDSCALE); if (p_path != "") { Ref rimd = ResourceLoader::load_import_metadata(p_path); ERR_FAIL_COND(!rimd.is_valid()); dest->get_line_edit()->set_text(p_path); List opts; rimd->get_options(&opts); options->reset(); for (List::Element *E = opts.front(); E; E = E->next()) { options->_set(E->get(), rimd->get_option(E->get())); } String src = ""; for (int i = 0; i < rimd->get_source_count(); i++) { if (i > 0) src += ","; src += EditorImportPlugin::expand_source_path(rimd->get_source_path(i)); } source->get_line_edit()->set_text(src); font_size->set_val(rimd->get_option("font/size")); } } void set_source_and_dest(const String &p_font, const String &p_dest) { source->get_line_edit()->set_text(p_font); dest->get_line_edit()->set_text(p_dest); _prop_changed(); } EditorFontImportDialog(EditorFontImportPlugin *p_plugin) { plugin = p_plugin; VBoxContainer *vbc = memnew(VBoxContainer); add_child(vbc); set_child_rect(vbc); HBoxContainer *hbc = memnew(HBoxContainer); vbc->add_child(hbc); VBoxContainer *vbl = memnew(VBoxContainer); hbc->add_child(vbl); hbc->set_v_size_flags(SIZE_EXPAND_FILL); vbl->set_h_size_flags(SIZE_EXPAND_FILL); VBoxContainer *vbr = memnew(VBoxContainer); hbc->add_child(vbr); vbr->set_h_size_flags(SIZE_EXPAND_FILL); source = memnew(EditorLineEditFileChooser); source->get_file_dialog()->set_access(EditorFileDialog::ACCESS_FILESYSTEM); source->get_file_dialog()->set_mode(EditorFileDialog::MODE_OPEN_FILE); source->get_file_dialog()->add_filter("*.ttf;TrueType"); source->get_file_dialog()->add_filter("*.otf;OpenType"); source->get_file_dialog()->add_filter("*.fnt;BMFont"); source->get_line_edit()->connect("text_entered", this, "_src_changed"); vbl->add_margin_child(TTR("Source Font:"), source); font_size = memnew(SpinBox); vbl->add_margin_child(TTR("Source Font Size:"), font_size); font_size->set_min(3); font_size->set_max(256); font_size->set_val(16); font_size->connect("value_changed", this, "_font_size_changed"); dest = memnew(EditorLineEditFileChooser); // List fl; Ref font = memnew(BitmapFont); dest->get_file_dialog()->add_filter("*.fnt ; Font"); //ResourceSaver::get_recognized_extensions(font,&fl); //for(List::Element *E=fl.front();E;E=E->next()) { // dest->get_file_dialog()->add_filter("*."+E->get()); //} vbl->add_margin_child(TTR("Dest Resource:"), dest); HBoxContainer *testhb = memnew(HBoxContainer); test_string = memnew(LineEdit); test_string->set_text(TTR("The quick brown fox jumps over the lazy dog.")); test_string->set_h_size_flags(SIZE_EXPAND_FILL); test_string->set_stretch_ratio(5); testhb->add_child(test_string); test_color = memnew(ColorPickerButton); test_color->set_color(get_color("font_color", "Label")); test_color->set_h_size_flags(SIZE_EXPAND_FILL); test_color->set_stretch_ratio(1); test_color->connect("color_changed", this, "_update_text3"); testhb->add_child(test_color); vbl->add_spacer(); vbl->add_margin_child(TTR("Test:") + " ", testhb); /* HBoxContainer *upd_hb = memnew( HBoxContainer ); // vbl->add_child(upd_hb); upd_hb->add_spacer(); Button *update = memnew( Button); upd_hb->add_child(update); update->set_text("Update"); update->connect("pressed",this,"_update"); */ options = memnew(_EditorFontImportOptions); prop_edit = memnew(PropertyEditor()); vbr->add_margin_child(TTR("Options:"), prop_edit, true); options->connect("changed", this, "_prop_changed"); prop_edit->hide_top_label(); Panel *panel = memnew(Panel); vbc->add_child(panel); test_label = memnew(Label); test_label->set_autowrap(true); panel->add_child(test_label); test_label->set_area_as_parent_rect(); panel->set_v_size_flags(SIZE_EXPAND_FILL); test_string->connect("text_changed", this, "_update_text2"); set_title(TTR("Font Import")); timer = memnew(Timer); add_child(timer); timer->connect("timeout", this, "_update"); timer->set_wait_time(0.4); timer->set_one_shot(true); get_ok()->connect("pressed", this, "_import"); get_ok()->set_text(TTR("Import")); error_dialog = memnew(ConfirmationDialog); add_child(error_dialog); error_dialog->get_ok()->set_text(TTR("Accept")); set_hide_on_ok(false); } ~EditorFontImportDialog() { memdelete(options); } }; /////////////////////////////////////// struct _EditorFontData { Vector bitmap; int width, height; int ofs_x; //ofset to center, from ABOVE int ofs_y; //ofset to begining, from LEFT int valign; //vertical alignment int halign; float advance; int character; int glyph; int texture; Image blit; Point2i blit_ofs; // bool printable; }; struct _EditorFontDataSort { bool operator()(const _EditorFontData *p_A, const _EditorFontData *p_B) const { return p_A->height > p_B->height; }; }; struct _EditorKerningKey { CharType A, B; bool operator<(const _EditorKerningKey &p_k) const { return (A == p_k.A) ? (B < p_k.B) : (A < p_k.A); } }; static unsigned char get_SDF_radial( unsigned char *fontmap, int w, int h, int x, int y, int max_radius) { // hideous brute force method float d2 = max_radius * max_radius + 1.0; unsigned char v = fontmap[x + y * w]; for (int radius = 1; (radius <= max_radius) && (radius * radius < d2); ++radius) { int line, lo, hi; // north line = y - radius; if ((line >= 0) && (line < h)) { lo = x - radius; hi = x + radius; if (lo < 0) { lo = 0; } if (hi >= w) { hi = w - 1; } int idx = line * w + lo; for (int i = lo; i <= hi; ++i) { // check this pixel if (fontmap[idx] != v) { float nx = i - x; float ny = line - y; float nd2 = nx * nx + ny * ny; if (nd2 < d2) { d2 = nd2; } } // move on ++idx; } } // south line = y + radius; if ((line >= 0) && (line < h)) { lo = x - radius; hi = x + radius; if (lo < 0) { lo = 0; } if (hi >= w) { hi = w - 1; } int idx = line * w + lo; for (int i = lo; i <= hi; ++i) { // check this pixel if (fontmap[idx] != v) { float nx = i - x; float ny = line - y; float nd2 = nx * nx + ny * ny; if (nd2 < d2) { d2 = nd2; } } // move on ++idx; } } // west line = x - radius; if ((line >= 0) && (line < w)) { lo = y - radius + 1; hi = y + radius - 1; if (lo < 0) { lo = 0; } if (hi >= h) { hi = h - 1; } int idx = lo * w + line; for (int i = lo; i <= hi; ++i) { // check this pixel if (fontmap[idx] != v) { float nx = line - x; float ny = i - y; float nd2 = nx * nx + ny * ny; if (nd2 < d2) { d2 = nd2; } } // move on idx += w; } } // east line = x + radius; if ((line >= 0) && (line < w)) { lo = y - radius + 1; hi = y + radius - 1; if (lo < 0) { lo = 0; } if (hi >= h) { hi = h - 1; } int idx = lo * w + line; for (int i = lo; i <= hi; ++i) { // check this pixel if (fontmap[idx] != v) { float nx = line - x; float ny = i - y; float nd2 = nx * nx + ny * ny; if (nd2 < d2) { d2 = nd2; } } // move on idx += w; } } } d2 = sqrtf(d2); if (v == 0) { d2 = -d2; } d2 *= 127.5 / max_radius; d2 += 127.5; if (d2 < 0.0) d2 = 0.0; if (d2 > 255.0) d2 = 255.0; return (unsigned char)(d2 + 0.5); } Ref EditorFontImportPlugin::generate_font(const Ref &p_from, const String &p_existing) { Ref from = p_from; ERR_FAIL_COND_V(from->get_source_count() != 1, Ref()); String src_path = EditorImportPlugin::expand_source_path(from->get_source_path(0)); if (src_path.extension().to_lower() == "fnt") { if (ResourceLoader::load(src_path).is_valid()) { EditorNode::get_singleton()->show_warning(TTR("Path:") + " " + src_path + "\n" + TTR("This file is already a Godot font file, please supply a BMFont type file instead.")); return Ref(); } Ref font; font.instance(); Error err = font->create_from_fnt(src_path); if (err) { EditorNode::get_singleton()->show_warning(TTR("Path:") + " " + src_path + "\n" + TTR("Failed opening as BMFont file.")); return Ref(); } return font; } int size = from->get_option("font/size"); #ifdef FREETYPE_ENABLED FT_Library library; /* handle to library */ FT_Face face; /* handle to face object */ Vector<_EditorFontData *> font_data_list; int error = FT_Init_FreeType(&library); ERR_EXPLAIN(TTR("Error initializing FreeType.")); ERR_FAIL_COND_V(error != 0, Ref()); print_line("loadfrom: " + src_path); error = FT_New_Face(library, src_path.utf8().get_data(), 0, &face); if (error == FT_Err_Unknown_File_Format) { ERR_EXPLAIN(TTR("Unknown font format.")); FT_Done_FreeType(library); } else if (error) { ERR_EXPLAIN(TTR("Error loading font.")); FT_Done_FreeType(library); } ERR_FAIL_COND_V(error, Ref()); int height = 0; int ascent = 0; int font_spacing = 0; error = FT_Set_Char_Size(face, 0, 64 * size, 512, 512); if (error) { FT_Done_FreeType(library); ERR_EXPLAIN(TTR("Invalid font size.")); ERR_FAIL_COND_V(error, Ref()); } int font_mode = from->get_option("mode/mode"); int scaler = (font_mode == _EditorFontImportOptions::FONT_DISTANCE_FIELD) ? 16 : 1; error = FT_Set_Pixel_Sizes(face, 0, size * scaler); FT_GlyphSlot slot = face->glyph; // error = FT_Set_Charmap(face,ft_encoding_unicode ); /* encoding.. */ /* PRINT CHARACTERS TO INDIVIDUAL BITMAPS */ // int space_size=5; //size for space, if none found.. 5! // int min_valign=500; //some ridiculous number FT_ULong charcode; FT_UInt gindex; int max_up = -1324345; ///gibberish int max_down = 124232; Map<_EditorKerningKey, int> kerning_map; charcode = FT_Get_First_Char(face, &gindex); Set import_chars; int import_mode = from->get_option("character_set/mode"); bool round_advance = from->get_option("advanced/round_advance"); if (import_mode >= _EditorFontImportOptions::CHARSET_CUSTOM) { //load from custom text String path = from->get_option("character_set/custom"); FileAccess *fa = FileAccess::open(EditorImportPlugin::expand_source_path(path), FileAccess::READ); if (!fa) { FT_Done_FreeType(library); ERR_EXPLAIN(TTR("Invalid font custom source.")); ERR_FAIL_COND_V(!fa, Ref()); } while (!fa->eof_reached()) { String line = fa->get_line(); for (int i = 0; i < line.length(); i++) { import_chars.insert(line[i]); } } if (import_mode == _EditorFontImportOptions::CHARSET_CUSTOM_LATIN) { for (int i = 32; i < 128; i++) import_chars.insert(i); } memdelete(fa); } int xsize = 0; while (gindex != 0) { bool skip = false; error = FT_Load_Char(face, charcode, font_mode == _EditorFontImportOptions::FONT_BITMAP ? FT_LOAD_RENDER : FT_LOAD_MONOCHROME); if (error) skip = true; else error = FT_Render_Glyph(face->glyph, font_mode == _EditorFontImportOptions::FONT_BITMAP ? ft_render_mode_normal : ft_render_mode_mono); if (error) { skip = true; } else if (!skip) { switch (import_mode) { case _EditorFontImportOptions::CHARSET_ASCII: skip = charcode > 127; break; case _EditorFontImportOptions::CHARSET_LATIN: skip = charcode > 255; break; case _EditorFontImportOptions::CHARSET_UNICODE: break; //none case _EditorFontImportOptions::CHARSET_CUSTOM: case _EditorFontImportOptions::CHARSET_CUSTOM_LATIN: skip = !import_chars.has(charcode); break; } } if (charcode <= 32) //?? skip = true; if (skip) { charcode = FT_Get_Next_Char(face, charcode, &gindex); continue; } _EditorFontData *fdata = memnew(_EditorFontData); int w = slot->bitmap.width; int h = slot->bitmap.rows; int p = slot->bitmap.pitch; //print_line("W: "+itos(w)+" P: "+itos(slot->bitmap.pitch)); if (font_mode == _EditorFontImportOptions::FONT_DISTANCE_FIELD) { // oversize the holding buffer so I can smooth it! int sw = w + scaler * 4; int sh = h + scaler * 4; // do the SDF int sdfw = sw / scaler; int sdfh = sh / scaler; fdata->width = sdfw; fdata->height = sdfh; } else { fdata->width = w; fdata->height = h; } fdata->character = charcode; fdata->glyph = FT_Get_Char_Index(face, charcode); if (charcode == 'x') xsize = w / scaler; fdata->valign = slot->bitmap_top; fdata->halign = slot->bitmap_left; if (round_advance) fdata->advance = (slot->advance.x + (1 << 5)) >> 6; else fdata->advance = slot->advance.x / float(1 << 6); if (font_mode == _EditorFontImportOptions::FONT_DISTANCE_FIELD) { fdata->halign = fdata->halign / scaler - 1.5; fdata->valign = fdata->valign / scaler + 1.5; fdata->advance /= scaler; } fdata->advance += font_spacing; if (charcode < 127) { int top = fdata->valign; int hmax = h / scaler; if (top > max_up) { max_up = top; } if ((top - hmax) < max_down) { max_down = top - hmax; } } if (font_mode == _EditorFontImportOptions::FONT_DISTANCE_FIELD) { // oversize the holding buffer so I can smooth it! int sw = w + scaler * 4; int sh = h + scaler * 4; unsigned char *smooth_buf = new unsigned char[sw * sh]; for (int i = 0; i < sw * sh; ++i) { smooth_buf[i] = 0; } // copy the glyph into the buffer to be smoothed unsigned char *buf = slot->bitmap.buffer; for (int j = 0; j < h; ++j) { for (int i = 0; i < w; ++i) { smooth_buf[scaler * 2 + i + (j + scaler * 2) * sw] = 255 * ((buf[j * p + (i >> 3)] >> (7 - (i & 7))) & 1); } } // do the SDF int sdfw = fdata->width; int sdfh = fdata->height; fdata->bitmap.resize(sdfw * sdfh); for (int j = 0; j < sdfh; ++j) { for (int i = 0; i < sdfw; ++i) { int pd_idx = j * sdfw + i; //fdata->bitmap[j*slot->bitmap.width+i]=slot->bitmap.buffer[j*slot->bitmap.width+i]; fdata->bitmap[pd_idx] = //get_SDF get_SDF_radial(smooth_buf, sw, sh, i * scaler + (scaler >> 1), j * scaler + (scaler >> 1), 2 * scaler); } } delete[] smooth_buf; } else { fdata->bitmap.resize(slot->bitmap.width * slot->bitmap.rows); for (int i = 0; i < slot->bitmap.width; i++) { for (int j = 0; j < slot->bitmap.rows; j++) { fdata->bitmap[j * slot->bitmap.width + i] = slot->bitmap.buffer[j * slot->bitmap.width + i]; } } } font_data_list.push_back(fdata); charcode = FT_Get_Next_Char(face, charcode, &gindex); // printf("reading char %i\n",charcode); } /* SPACE */ _EditorFontData *spd = memnew(_EditorFontData); spd->advance = 0; spd->character = ' '; spd->halign = 0; spd->valign = 0; spd->width = 0; spd->height = 0; spd->ofs_x = 0; spd->ofs_y = 0; if (!FT_Load_Char(face, ' ', FT_LOAD_RENDER) && !FT_Render_Glyph(face->glyph, font_mode == _EditorFontImportOptions::FONT_BITMAP ? ft_render_mode_normal : ft_render_mode_mono)) { spd->advance = slot->advance.x >> 6; //round to nearest or store as float spd->advance /= scaler; spd->advance += font_spacing; } else { spd->advance = xsize; spd->advance += font_spacing; } font_data_list.push_back(spd); Set exported; for (int i = 0; i < font_data_list.size(); i++) { exported.insert(font_data_list[i]->character); }; int missing = 0; for (Set::Element *E = import_chars.front(); E; E = E->next()) { CharType c = E->get(); if (!exported.has(c)) { CharType str[2] = { c, 0 }; printf("** Warning: character %i (%ls) not exported\n", (int)c, str); ++missing; }; }; print_line("total_chars: " + itos(font_data_list.size())); /* KERNING */ for (int i = 0; i < font_data_list.size(); i++) { if (font_data_list[i]->character > 512) continue; for (int j = 0; j < font_data_list.size(); j++) { if (font_data_list[j]->character > 512) continue; FT_Vector delta; FT_Get_Kerning(face, font_data_list[i]->glyph, font_data_list[j]->glyph, FT_KERNING_DEFAULT, &delta); if (delta.x != 0) { _EditorKerningKey kpk; kpk.A = font_data_list[i]->character; kpk.B = font_data_list[j]->character; int kern = ((-delta.x) + (1 << 5)) >> 6; if (kern == 0) continue; kerning_map[kpk] = kern / scaler; } } } height = max_up - max_down; ascent = max_up; /* FIND OUT WHAT THE FONT HEIGHT FOR THIS IS */ /* ADJUST THE VALIGN FOR EACH CHARACTER */ for (int i = 0; i < (int)font_data_list.size(); i++) { font_data_list[i]->valign = max_up - font_data_list[i]->valign; } /* ADD THE SPACEBAR CHARACTER */ /* _EditorFontData * fdata = new _EditorFontData; fdata->character=32; fdata->bitmap=0; fdata->width=xsize; fdata->height=1; fdata->valign=0; font_data_list.push_back(fdata); */ /* SORT BY HEIGHT, SO THEY FIT BETTER ON THE TEXTURE */ font_data_list.sort_custom<_EditorFontDataSort>(); Color *color = memnew_arr(Color, height); int gradient_type = from->get_option("color/mode"); switch (gradient_type) { case _EditorFontImportOptions::COLOR_WHITE: { for (int i = 0; i < height; i++) { color[i] = Color(1, 1, 1, 1); } } break; case _EditorFontImportOptions::COLOR_CUSTOM: { Color cc = from->get_option("color/color"); for (int i = 0; i < height; i++) { color[i] = cc; } } break; case _EditorFontImportOptions::COLOR_GRADIENT_RANGE: { Color src = from->get_option("color/begin"); Color to = from->get_option("color/end"); for (int i = 0; i < height; i++) { color[i] = src.linear_interpolate(to, i / float(height)); } } break; case _EditorFontImportOptions::COLOR_GRADIENT_IMAGE: { String fp = EditorImportPlugin::expand_source_path(from->get_option("color/image")); Image img; Error err = ImageLoader::load_image(fp, &img); if (err == OK) { for (int i = 0; i < height; i++) { color[i] = img.get_pixel(0, i * img.get_height() / height); } } else { for (int i = 0; i < height; i++) { color[i] = Color(1, 1, 1, 1); } } } break; } for (int i = 0; i < font_data_list.size(); i++) { if (font_data_list[i]->bitmap.size() == 0) continue; int margin[4] = { 0, 0, 0, 0 }; if (from->get_option("shadow/enabled").operator bool()) { int r = from->get_option("shadow/radius"); Point2i ofs = Point2(from->get_option("shadow/offset")); margin[MARGIN_LEFT] = MAX(r - ofs.x, 0); margin[MARGIN_RIGHT] = MAX(r + ofs.x, 0); margin[MARGIN_TOP] = MAX(r - ofs.y, 0); margin[MARGIN_BOTTOM] = MAX(r + ofs.y, 0); } if (from->get_option("shadow2/enabled").operator bool()) { int r = from->get_option("shadow2/radius"); Point2i ofs = Point2(from->get_option("shadow2/offset")); margin[MARGIN_LEFT] = MAX(r - ofs.x, margin[MARGIN_LEFT]); margin[MARGIN_RIGHT] = MAX(r + ofs.x, margin[MARGIN_RIGHT]); margin[MARGIN_TOP] = MAX(r - ofs.y, margin[MARGIN_TOP]); margin[MARGIN_BOTTOM] = MAX(r + ofs.y, margin[MARGIN_BOTTOM]); } Size2i s; s.width = font_data_list[i]->width + margin[MARGIN_LEFT] + margin[MARGIN_RIGHT]; s.height = font_data_list[i]->height + margin[MARGIN_TOP] + margin[MARGIN_BOTTOM]; Point2i o; o.x = margin[MARGIN_LEFT]; o.y = margin[MARGIN_TOP]; int ow = font_data_list[i]->width; int oh = font_data_list[i]->height; DVector pixels; pixels.resize(s.x * s.y * 4); DVector::Write w = pixels.write(); //print_line("val: "+itos(font_data_list[i]->valign)); for (int y = 0; y < s.height; y++) { int yc = CLAMP(y - o.y + font_data_list[i]->valign, 0, height - 1); Color c = color[yc]; c.a = 0; for (int x = 0; x < s.width; x++) { int ofs = y * s.x + x; w[ofs * 4 + 0] = c.r * 255.0; w[ofs * 4 + 1] = c.g * 255.0; w[ofs * 4 + 2] = c.b * 255.0; w[ofs * 4 + 3] = c.a * 255.0; } } for (int si = 0; si < 2; si++) { #define S_VAR(m_v) (String(si == 0 ? "shadow/" : "shadow2/") + m_v) if (from->get_option(S_VAR("enabled")).operator bool()) { int r = from->get_option(S_VAR("radius")); Color sc = from->get_option(S_VAR("color")); Point2i so = Point2(from->get_option(S_VAR("offset"))); float tr = from->get_option(S_VAR("transition")); print_line("shadow enabled: " + itos(si)); Vector s2buf; s2buf.resize(s.x * s.y); uint8_t *wa = s2buf.ptr(); for (int j = 0; j < s.x * s.y; j++) { wa[j] = 0; } // blit shadowa for (int x = 0; x < ow; x++) { for (int y = 0; y < oh; y++) { int ofs = (o.y + y + so.y) * s.x + x + o.x + so.x; wa[ofs] = font_data_list[i]->bitmap[y * ow + x]; } } //blur shadow2 with separatable convolution if (r > 0) { Vector pixels2; pixels2.resize(s2buf.size()); uint8_t *w2 = pixels2.ptr(); //vert for (int x = 0; x < s.width; x++) { for (int y = 0; y < s.height; y++) { int ofs = y * s.width + x; int sum = wa[ofs]; for (int k = 1; k <= r; k++) { int ofs_d = MIN(y + k, s.height - 1) * s.width + x; int ofs_u = MAX(y - k, 0) * s.width + x; sum += wa[ofs_d]; sum += wa[ofs_u]; } w2[ofs] = sum / (r * 2 + 1); } } //horiz for (int x = 0; x < s.width; x++) { for (int y = 0; y < s.height; y++) { int ofs = y * s.width + x; int sum = w2[ofs]; for (int k = 1; k <= r; k++) { int ofs_r = MIN(x + k, s.width - 1) + s.width * y; int ofs_l = MAX(x - k, 0) + s.width * y; sum += w2[ofs_r]; sum += w2[ofs_l]; } wa[ofs] = Math::pow(float(sum / (r * 2 + 1)) / 255.0, tr) * 255.0; } } } //blend back for (int j = 0; j < s.x * s.y; j++) { Color wd(w[j * 4 + 0] / 255.0, w[j * 4 + 1] / 255.0, w[j * 4 + 2] / 255.0, w[j * 4 + 3] / 255.0); Color ws(sc.r, sc.g, sc.b, sc.a * (wa[j] / 255.0)); Color b = wd.blend(ws); w[j * 4 + 0] = b.r * 255.0; w[j * 4 + 1] = b.g * 255.0; w[j * 4 + 2] = b.b * 255.0; w[j * 4 + 3] = b.a * 255.0; } } } for (int y = 0; y < oh; y++) { int yc = CLAMP(y + font_data_list[i]->valign, 0, height - 1); Color sc = color[yc]; for (int x = 0; x < ow; x++) { int ofs = (o.y + y) * s.x + x + o.x; float c = font_data_list[i]->bitmap[y * ow + x] / 255.0; Color src_col = sc; src_col.a *= c; Color dst_col(w[ofs * 4 + 0] / 255.0, w[ofs * 4 + 1] / 255.0, w[ofs * 4 + 2] / 255.0, w[ofs * 4 + 3] / 255.0); dst_col = dst_col.blend(src_col); w[ofs * 4 + 0] = dst_col.r * 255.0; w[ofs * 4 + 1] = dst_col.g * 255.0; w[ofs * 4 + 2] = dst_col.b * 255.0; w[ofs * 4 + 3] = dst_col.a * 255.0; } } w = DVector::Write(); Image img(s.width, s.height, 0, Image::FORMAT_RGBA, pixels); font_data_list[i]->blit = img; font_data_list[i]->blit_ofs = o; } //make atlas int spacing = 2; Vector sizes; sizes.resize(font_data_list.size()); for (int i = 0; i < font_data_list.size(); i++) { sizes[i] = Size2(font_data_list[i]->blit.get_width() + spacing * 2, font_data_list[i]->blit.get_height() + spacing * 2); } Vector res; Size2i res_size; EditorAtlas::fit(sizes, res, res_size); res_size.x = next_power_of_2(res_size.x); res_size.y = next_power_of_2(res_size.y); print_line("Atlas size: " + res_size); Image atlas(res_size.x, res_size.y, 0, Image::FORMAT_RGBA); for (int i = 0; i < font_data_list.size(); i++) { if (font_data_list[i]->bitmap.size() == 0) continue; atlas.blit_rect(font_data_list[i]->blit, Rect2(0, 0, font_data_list[i]->blit.get_width(), font_data_list[i]->blit.get_height()), res[i] + Size2(spacing, spacing)); font_data_list[i]->ofs_x = res[i].x + spacing; font_data_list[i]->ofs_y = res[i].y + spacing; } if (from->has_option("advanced/premultiply_alpha") && bool(from->get_option("advanced/premultiply_alpha"))) { DVector data = atlas.get_data(); int dl = data.size(); { DVector::Write w = data.write(); for (int i = 0; i < dl; i += 4) { w[i + 0] = uint8_t(int(w[i + 0]) * int(w[i + 3]) / 255); w[i + 1] = uint8_t(int(w[i + 1]) * int(w[i + 3]) / 255); w[i + 2] = uint8_t(int(w[i + 2]) * int(w[i + 3]) / 255); } } atlas = Image(res_size.x, res_size.y, 0, Image::FORMAT_RGBA, data); } if (from->has_option("color/monochrome") && bool(from->get_option("color/monochrome"))) { atlas.convert(Image::FORMAT_GRAYSCALE_ALPHA); } if (0) { //debug the texture Ref atlast = memnew(ImageTexture); atlast->create_from_image(atlas); // atlast->create_from_image(font_data_list[5]->blit); TextureFrame *tf = memnew(TextureFrame); tf->set_texture(atlast); dialog->add_child(tf); } /* CREATE FONT */ int char_space = from->get_option("extra_space/char"); int space_space = from->get_option("extra_space/space"); int top_space = from->get_option("extra_space/top"); int bottom_space = from->get_option("extra_space/bottom"); bool disable_filter = from->get_option("advanced/disable_filter"); Ref font; if (p_existing != String() && ResourceCache::has(p_existing)) { font = Ref(ResourceCache::get(p_existing)->cast_to()); } if (font.is_null()) { font = Ref(memnew(BitmapFont)); } font->clear(); font->set_height(height + bottom_space + top_space); font->set_ascent(ascent + top_space); font->set_distance_field_hint(font_mode == _EditorFontImportOptions::FONT_DISTANCE_FIELD); //register texures { Ref t = memnew(ImageTexture); int flags; if (disable_filter) flags = 0; else flags = Texture::FLAG_FILTER; t->create_from_image(atlas, flags); t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSLESS); font->add_texture(t); } //register characters for (int i = 0; i < font_data_list.size(); i++) { _EditorFontData *fd = font_data_list[i]; int tex_idx = 0; font->add_char(fd->character, tex_idx, Rect2(fd->ofs_x, fd->ofs_y, fd->blit.get_width(), fd->blit.get_height()), Point2(fd->halign - fd->blit_ofs.x, fd->valign - fd->blit_ofs.y + top_space), fd->advance + char_space + (fd->character == ' ' ? space_space : 0)); memdelete(fd); } for (Map<_EditorKerningKey, int>::Element *E = kerning_map.front(); E; E = E->next()) { font->add_kerning_pair(E->key().A, E->key().B, E->get()); } FT_Done_FreeType(library); return font; #else return Ref(); #endif } String EditorFontImportPlugin::get_name() const { return "font"; } String EditorFontImportPlugin::get_visible_name() const { return TTR("Font"); } void EditorFontImportPlugin::import_dialog(const String &p_from) { dialog->popup_import(p_from); } Error EditorFontImportPlugin::import(const String &p_path, const Ref &p_from) { Ref font = EditorFontImportPlugin::generate_font(p_from, p_path); if (!font.is_valid()) return ERR_CANT_CREATE; Ref from = p_from; from->set_source_md5(0, FileAccess::get_md5(EditorImportPlugin::expand_source_path(from->get_source_path(0)))); from->set_editor(get_name()); font->set_import_metadata(from); return ResourceSaver::save(p_path, font); } void EditorFontImportPlugin::import_from_drop(const Vector &p_drop, const String &p_dest_path) { for (int i = 0; i < p_drop.size(); i++) { String ext = p_drop[i].extension().to_lower(); String file = p_drop[i].get_file(); if (ext == "ttf" || ext == "otf" || ext == "fnt") { import_dialog(); dialog->set_source_and_dest(p_drop[i], p_dest_path.plus_file(file.basename() + ".fnt")); break; } } } EditorFontImportPlugin::EditorFontImportPlugin(EditorNode *p_editor) { dialog = memnew(EditorFontImportDialog(this)); p_editor->get_gui_base()->add_child(dialog); }