godot/tools/editor/io_plugins/editor_font_import_plugin.cpp
Juan Linietsky 6fc1c3a4d1 Completed the support for plugins! It is not possible to add plugins.
Not all APIs are provided yet, please request whathever you are missing.
Some example plugins are provided in demos/plugins. Just copy them to a folder in your project named addons/ and then enable them from the project settings.
Have fun!
2016-02-27 23:12:27 -03:00

1638 lines
42 KiB
C++

/*************************************************************************/
/* editor_font_import_plugin.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* http://www.godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "scene/gui/dialogs.h"
#include "tools/editor/editor_file_dialog.h"
#include "tools/editor/editor_node.h"
#include "os/file_access.h"
#include "editor_atlas.h"
#include "io/image_loader.h"
#include "io/resource_saver.h"
#ifdef FREETYPE_ENABLED
#include <ft2build.h>
#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 _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
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
return false;
return true;
}
void _get_property_list( List<PropertyInfo> *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_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_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"));
}
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;
}
_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;
}
};
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<ResourceImportMetadata> get_rimd() {
Ref<ResourceImportMetadata> imd = memnew( ResourceImportMetadata );
List<PropertyInfo> pl;
options->_get_property_list(&pl);
for(List<PropertyInfo>::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<ResourceImportMetadata> imd = get_rimd();
Ref<Font> 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> font = ResourceLoader::load(p_font);
if (!font.is_valid())
return;
Ref<ImageTexture> tex = font->get_texture(0);
if (tex.is_null())
return;
FileAccessRef f=FileAccess::open(p_font.basename()+".inc",FileAccess::WRITE);
Vector<CharType> 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];
Font::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<Font::KerningPairKey> 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())+";");
f->store_line("static const unsigned char _builtin_font_img_data["+itos(img.get_width()*img.get_height()*2)+"]={");
for(int i=0;i<img.get_height();i++) {
for(int j=0;j<img.get_width();j++) {
Color c = img.get_pixel(j,i);
int v = CLAMP(((c.r+c.g+c.b)/3.0)*255,0,255);
int a = CLAMP(c.a*255,0,255);
f->store_line(itos(v)+","+itos(a)+",");
}
}
f->store_line("};");
}
void _import() {
if (source->get_line_edit()->get_text()=="") {
error_dialog->set_text("No source font file!");
error_dialog->popup_centered(Size2(200,100));
return;
}
if (dest->get_line_edit()->get_text()=="") {
error_dialog->set_text("No target font resource!");
error_dialog->popup_centered(Size2(200,100));
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" );
}
Ref<ResourceImportMetadata> rimd = get_rimd();
if (rimd.is_null()) {
error_dialog->set_text("Can't load/process source font");
error_dialog->popup_centered(Size2(200,100));
return;
}
Error err = plugin->import(dest->get_line_edit()->get_text(),rimd);
if (err!=OK) {
error_dialog->set_text("Couldn't save font.");
error_dialog->popup_centered(Size2(200,100));
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));
if (p_path!="") {
Ref<ResourceImportMetadata> rimd = ResourceLoader::load_import_metadata(p_path);
ERR_FAIL_COND(!rimd.is_valid());
dest->get_line_edit()->set_text(p_path);
List<String> opts;
rimd->get_options(&opts);
options->reset();
for(List<String>::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"));
}
}
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("Source Font:",source);
font_size = memnew( SpinBox );
vbl->add_margin_child("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<String> fl;
Ref<Font> font= memnew(Font);
dest->get_file_dialog()->add_filter("*.fnt ; Font" );
//ResourceSaver::get_recognized_extensions(font,&fl);
//for(List<String>::Element *E=fl.front();E;E=E->next()) {
// dest->get_file_dialog()->add_filter("*."+E->get());
//}
vbl->add_margin_child("Dest Resource:",dest);
HBoxContainer *testhb = memnew( HBoxContainer );
test_string = memnew( LineEdit );
test_string->set_text("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("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("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("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("Import");
error_dialog = memnew ( ConfirmationDialog );
add_child(error_dialog);
error_dialog->get_ok()->set_text("Accept");
set_hide_on_ok(false);
}
~EditorFontImportDialog() {
memdelete(options);
}
};
///////////////////////////////////////
struct _EditorFontData {
Vector<uint8_t> 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<Font> EditorFontImportPlugin::generate_font(const Ref<ResourceImportMetadata>& p_from, const String &p_existing) {
Ref<ResourceImportMetadata> from = p_from;
ERR_FAIL_COND_V(from->get_source_count()!=1,Ref<Font>());
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("Path: "+src_path+"\nIs a Godot font file, please supply a BMFont type file instead.");
return Ref<Font>();
}
Ref<Font> font;
font.instance();
Error err = font->create_from_fnt(src_path);
if (err) {
EditorNode::get_singleton()->show_warning("Path: "+src_path+"\nFailed opening as BMFont file.");
return Ref<Font>();
}
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("Error initializing FreeType.");
ERR_FAIL_COND_V( error !=0, Ref<Font>() );
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("Unknown font format.");
FT_Done_FreeType( library );
} else if ( error ) {
ERR_EXPLAIN("Error loading font.");
FT_Done_FreeType( library );
}
ERR_FAIL_COND_V(error,Ref<Font>());
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("Invalid font size. ");
ERR_FAIL_COND_V( error,Ref<Font>() );
}
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<CharType> 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("Invalid font custom source. ");
ERR_FAIL_COND_V( !fa,Ref<Font>() );
}
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<CharType> exported;
for (int i=0; i<font_data_list.size(); i++) {
exported.insert(font_data_list[i]->character);
};
int missing = 0;
for(Set<CharType>::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<uint8_t> pixels;
pixels.resize(s.x*s.y*4);
DVector<uint8_t>::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<uint8_t> 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<uint8_t> 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<uint8_t>::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<Size2i> 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<Point2i> res;
Size2i res_size;
EditorAtlas::fit(sizes,res,res_size);
res_size.x=nearest_power_of_2(res_size.x);
res_size.y=nearest_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("color/monochrome") && bool(from->get_option("color/monochrome"))) {
atlas.convert(Image::FORMAT_GRAYSCALE_ALPHA);
}
if (0) {
//debug the texture
Ref<ImageTexture> 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> font;
if (p_existing!=String() && ResourceCache::has(p_existing)) {
font = Ref<Font>( ResourceCache::get(p_existing)->cast_to<Font>());
}
if (font.is_null()) {
font = Ref<Font>( memnew( Font ) );
}
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<ImageTexture> 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<Font>();
#endif
}
String EditorFontImportPlugin::get_name() const {
return "font";
}
String EditorFontImportPlugin::get_visible_name() const{
return "Font";
}
void EditorFontImportPlugin::import_dialog(const String& p_from){
dialog->popup_import(p_from);
}
Error EditorFontImportPlugin::import(const String& p_path, const Ref<ResourceImportMetadata>& p_from){
Ref<Font> font = EditorFontImportPlugin::generate_font(p_from,p_path);
if (!font.is_valid())
return ERR_CANT_CREATE;
Ref<ResourceImportMetadata> 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);
}
EditorFontImportPlugin::EditorFontImportPlugin(EditorNode* p_editor) {
dialog = memnew( EditorFontImportDialog(this) );
p_editor->get_gui_base()->add_child(dialog);
}