/*************************************************************************/ /* dynamic_font.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2017 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. */ /*************************************************************************/ #ifdef FREETYPE_ENABLED #include "dynamic_font.h" #include "os/file_access.h" #include "os/os.h" bool DynamicFontData::CacheID::operator<(CacheID right) const { if (size < right.size) return true; if (mipmaps != right.mipmaps) return right.mipmaps; if (filter != right.filter) return right.filter; return false; } Ref DynamicFontData::_get_dynamic_font_at_size(CacheID p_id) { if (size_cache.has(p_id)) { return Ref(size_cache[p_id]); } Ref dfas; dfas.instance(); dfas->font = Ref(this); size_cache[p_id] = dfas.ptr(); dfas->id = p_id; dfas->_load(); return dfas; } void DynamicFontData::set_font_ptr(const uint8_t *p_font_mem, int p_font_mem_size) { font_mem = p_font_mem; font_mem_size = p_font_mem_size; } void DynamicFontData::set_font_path(const String &p_path) { font_path = p_path; } String DynamicFontData::get_font_path() const { return font_path; } void DynamicFontData::set_force_autohinter(bool p_force) { force_autohinter = p_force; } void DynamicFontData::_bind_methods() { ObjectTypeDB::bind_method(_MD("set_font_path", "path"), &DynamicFontData::set_font_path); ObjectTypeDB::bind_method(_MD("get_font_path"), &DynamicFontData::get_font_path); ADD_PROPERTY(PropertyInfo(Variant::STRING, "font_path", PROPERTY_HINT_FILE, "*.ttf,*.otf"), _SCS("set_font_path"), _SCS("get_font_path")); } DynamicFontData::DynamicFontData() { force_autohinter = false; font_mem = NULL; font_mem_size = 0; } DynamicFontData::~DynamicFontData() { } //////////////////// HashMap > DynamicFontAtSize::_fontdata; Error DynamicFontAtSize::_load() { int error = FT_Init_FreeType(&library); ERR_EXPLAIN(TTR("Error initializing FreeType.")); ERR_FAIL_COND_V(error != 0, ERR_CANT_CREATE); // FT_OPEN_STREAM is extremely slow only on Android. if (OS::get_singleton()->get_name() == "Android" && font->font_mem == NULL && font->font_path != String()) { // cache font only once for each font->font_path if (_fontdata.has(font->font_path)) { font->set_font_ptr(_fontdata[font->font_path].ptr(), _fontdata[font->font_path].size()); } else { FileAccess *f = FileAccess::open(font->font_path, FileAccess::READ); ERR_FAIL_COND_V(!f, ERR_CANT_OPEN); size_t len = f->get_len(); _fontdata[font->font_path] = Vector(); Vector &fontdata = _fontdata[font->font_path]; fontdata.resize(len); f->get_buffer(fontdata.ptr(), len); font->set_font_ptr(fontdata.ptr(), len); f->close(); } } if (font->font_mem == NULL && font->font_path != String()) { FileAccess *f = FileAccess::open(font->font_path, FileAccess::READ); ERR_FAIL_COND_V(!f, ERR_CANT_OPEN); memset(&stream, 0, sizeof(FT_StreamRec)); stream.base = NULL; stream.size = f->get_len(); stream.pos = 0; stream.descriptor.pointer = f; stream.read = _ft_stream_io; stream.close = _ft_stream_close; FT_Open_Args fargs; memset(&fargs, 0, sizeof(FT_Open_Args)); fargs.flags = FT_OPEN_STREAM; fargs.stream = &stream; error = FT_Open_Face(library, &fargs, 0, &face); } else if (font->font_mem) { memset(&stream, 0, sizeof(FT_StreamRec)); stream.base = (unsigned char *)font->font_mem; stream.size = font->font_mem_size; stream.pos = 0; FT_Open_Args fargs; memset(&fargs, 0, sizeof(FT_Open_Args)); fargs.memory_base = (unsigned char *)font->font_mem; fargs.memory_size = font->font_mem_size; fargs.flags = FT_OPEN_MEMORY; fargs.stream = &stream; error = FT_Open_Face(library, &fargs, 0, &face); } else { ERR_EXPLAIN("DynamicFont uninitialized"); ERR_FAIL_V(ERR_UNCONFIGURED); } //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, ERR_FILE_CANT_OPEN); /*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, ERR_INVALID_PARAMETER ); }*/ error = FT_Set_Pixel_Sizes(face, 0, id.size); ascent = face->size->metrics.ascender >> 6; descent = -face->size->metrics.descender >> 6; linegap = 0; texture_flags = 0; if (id.mipmaps) texture_flags |= Texture::FLAG_MIPMAPS; if (id.filter) texture_flags |= Texture::FLAG_FILTER; //print_line("ASCENT: "+itos(ascent)+" descent "+itos(descent)+" hinted: "+itos(face->face_flags&FT_FACE_FLAG_HINTER)); valid = true; return OK; } float DynamicFontAtSize::get_height() const { return ascent + descent; } float DynamicFontAtSize::get_ascent() const { return ascent; } float DynamicFontAtSize::get_descent() const { return descent; } Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next, const Vector > &p_fallbacks) const { if (!valid) return Size2(1, 1); const_cast(this)->_update_char(p_char); const Character *c = char_map.getptr(p_char); ERR_FAIL_COND_V(!c, Size2()); Size2 ret(0, get_height()); if (!c->found) { //not found, try in fallbacks for (int i = 0; i < p_fallbacks.size(); i++) { DynamicFontAtSize *fb = const_cast(p_fallbacks[i].ptr()); if (!fb->valid) continue; fb->_update_char(p_char); const Character *ch = fb->char_map.getptr(p_char); ERR_CONTINUE(!ch); if (!ch->found) continue; c = ch; break; } //not found, try 0xFFFD to display 'not found'. if (!c->found) { const_cast(this)->_update_char(0xFFFD); c = char_map.getptr(0xFFFD); ERR_FAIL_COND_V(!c, Size2()); } } if (c->found) { ret.x = c->advance; } if (p_next) { FT_Vector delta; FT_Get_Kerning(face, p_char, p_next, FT_KERNING_DEFAULT, &delta); if (delta.x == 0) { for (int i = 0; i < p_fallbacks.size(); i++) { DynamicFontAtSize *fb = const_cast(p_fallbacks[i].ptr()); if (!fb->valid) continue; FT_Get_Kerning(fb->face, p_char, p_next, FT_KERNING_DEFAULT, &delta); if (delta.x == 0) continue; ret.x += delta.x >> 6; break; } } else { ret.x += delta.x >> 6; } } return ret; } void DynamicFontAtSize::set_texture_flags(uint32_t p_flags) { texture_flags = p_flags; for (int i = 0; i < textures.size(); i++) { Ref &tex = textures[i].texture; if (!tex.is_null()) tex->set_flags(p_flags); } } float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector > &p_fallbacks) const { if (!valid) return 0; const_cast(this)->_update_char(p_char); const Character *c = char_map.getptr(p_char); float advance = 0; if (!c->found) { //not found, try in fallbacks bool used_fallback = false; for (int i = 0; i < p_fallbacks.size(); i++) { DynamicFontAtSize *fb = const_cast(p_fallbacks[i].ptr()); if (!fb->valid) continue; fb->_update_char(p_char); const Character *ch = fb->char_map.getptr(p_char); ERR_CONTINUE(!ch); if (!ch->found) continue; Point2 cpos = p_pos; cpos.x += ch->h_align; cpos.y -= get_ascent(); cpos.y += ch->v_align; ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= fb->textures.size(), 0); if (ch->texture_idx != -1) VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, ch->rect.size), fb->textures[ch->texture_idx].texture->get_rid(), ch->rect, p_modulate); advance = ch->advance; used_fallback = true; break; } //not found, try 0xFFFD to display 'not found'. if (!used_fallback) { const_cast(this)->_update_char(0xFFFD); c = char_map.getptr(0xFFFD); } } if (c->found) { Point2 cpos = p_pos; cpos.x += c->h_align; cpos.y -= get_ascent(); cpos.y += c->v_align; ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), 0); if (c->texture_idx != -1) VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size), textures[c->texture_idx].texture->get_rid(), c->rect, p_modulate); advance = c->advance; //textures[c->texture_idx].texture->draw(p_canvas_item,Vector2()); } if (p_next) { FT_Vector delta; FT_Get_Kerning(face, p_char, p_next, FT_KERNING_DEFAULT, &delta); if (delta.x == 0) { for (int i = 0; i < p_fallbacks.size(); i++) { DynamicFontAtSize *fb = const_cast(p_fallbacks[i].ptr()); if (!fb->valid) continue; FT_Get_Kerning(fb->face, p_char, p_next, FT_KERNING_DEFAULT, &delta); if (delta.x == 0) continue; advance += delta.x >> 6; break; } } else { advance += delta.x >> 6; } } return advance; } unsigned long DynamicFontAtSize::_ft_stream_io(FT_Stream stream, unsigned long offset, unsigned char *buffer, unsigned long count) { FileAccess *f = (FileAccess *)stream->descriptor.pointer; if (f->get_pos() != offset) { f->seek(offset); } if (count == 0) return 0; return f->get_buffer(buffer, count); } void DynamicFontAtSize::_ft_stream_close(FT_Stream stream) { FileAccess *f = (FileAccess *)stream->descriptor.pointer; f->close(); memdelete(f); } void DynamicFontAtSize::_update_char(CharType p_char) { if (char_map.has(p_char)) return; _THREAD_SAFE_METHOD_ FT_GlyphSlot slot = face->glyph; if (FT_Get_Char_Index(face, p_char) == 0) { //not found Character ch; ch.texture_idx = -1; ch.advance = 0; ch.h_align = 0; ch.v_align = 0; ch.found = false; char_map[p_char] = ch; return; } int error = FT_Load_Char(face, p_char, FT_LOAD_RENDER | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); if (!error) { error = FT_Render_Glyph(face->glyph, ft_render_mode_normal); } if (error) { int advance = 0; //stbtt_GetCodepointHMetrics(&font->info, p_char, &advance, 0); //print_line("char has no bitmap: "+itos(p_char)+" but advance is "+itos(advance*scale)); Character ch; ch.texture_idx = -1; ch.advance = advance; ch.h_align = 0; ch.v_align = 0; ch.found = false; char_map[p_char] = ch; return; } int w = slot->bitmap.width; int h = slot->bitmap.rows; //int p = slot->bitmap.pitch; int yofs = slot->bitmap_top; int xofs = slot->bitmap_left; int advance = slot->advance.x >> 6; int mw = w + rect_margin * 2; int mh = h + rect_margin * 2; if (mw > 4096 || mh > 4096) { ERR_FAIL_COND(mw > 4096); ERR_FAIL_COND(mh > 4096); } //find a texture to fit this... int tex_index = -1; int tex_x = 0; int tex_y = 0; for (int i = 0; i < textures.size(); i++) { CharTexture &ct = textures[i]; if (mw > ct.texture_size || mh > ct.texture_size) //too big for this texture continue; tex_y = 0x7FFFFFFF; tex_x = 0; for (int j = 0; j < ct.texture_size - mw; j++) { int max_y = 0; for (int k = j; k < j + mw; k++) { int y = ct.offsets[k]; if (y > max_y) max_y = y; } if (max_y < tex_y) { tex_y = max_y; tex_x = j; } } if (tex_y == 0x7FFFFFFF || tex_y + mh > ct.texture_size) continue; //fail, could not fit it here tex_index = i; break; } // print_line("CHAR: "+String::chr(p_char)+" TEX INDEX: "+itos(tex_index)+" X: "+itos(tex_x)+" Y: "+itos(tex_y)); if (tex_index == -1) { //could not find texture to fit, create one tex_x = 0; tex_y = 0; int texsize = MAX(id.size * 8, 256); if (mw > texsize) texsize = mw; //special case, adapt to it? if (mh > texsize) texsize = mh; //special case, adapt to it? texsize = nearest_power_of_2(texsize); texsize = MIN(texsize, 4096); CharTexture tex; tex.texture_size = texsize; tex.imgdata.resize(texsize * texsize * 2); //grayscale alpha { //zero texture DVector::Write w = tex.imgdata.write(); ERR_FAIL_COND(texsize * texsize * 2 > tex.imgdata.size()); for (int i = 0; i < texsize * texsize * 2; i++) { w[i] = 0; } } tex.offsets.resize(texsize); for (int i = 0; i < texsize; i++) //zero offsets tex.offsets[i] = 0; textures.push_back(tex); tex_index = textures.size() - 1; } //fit character in char texture CharTexture &tex = textures[tex_index]; { DVector::Write wr = tex.imgdata.write(); for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { int ofs = ((i + tex_y + rect_margin) * tex.texture_size + j + tex_x + rect_margin) * 2; ERR_FAIL_COND(ofs >= tex.imgdata.size()); wr[ofs + 0] = 255; //grayscale as 1 wr[ofs + 1] = slot->bitmap.buffer[i * slot->bitmap.width + j]; } } } //blit to image and texture { Image img(tex.texture_size, tex.texture_size, 0, Image::FORMAT_GRAYSCALE_ALPHA, tex.imgdata); if (tex.texture.is_null()) { tex.texture.instance(); tex.texture->create_from_image(img, Texture::FLAG_VIDEO_SURFACE | texture_flags); } else { tex.texture->set_data(img); //update } } // update height array for (int k = tex_x; k < tex_x + mw; k++) { tex.offsets[k] = tex_y + mh; } Character chr; chr.h_align = xofs; chr.v_align = ascent - yofs; // + ascent - descent; chr.advance = advance; chr.texture_idx = tex_index; chr.found = true; chr.rect = Rect2(tex_x + rect_margin, tex_y + rect_margin, w, h); //print_line("CHAR: "+String::chr(p_char)+" TEX INDEX: "+itos(tex_index)+" RECT: "+chr.rect+" X OFS: "+itos(xofs)+" Y OFS: "+itos(yofs)); char_map[p_char] = chr; } DynamicFontAtSize::DynamicFontAtSize() { valid = false; rect_margin = 1; ascent = 1; descent = 1; linegap = 1; texture_flags = 0; } DynamicFontAtSize::~DynamicFontAtSize() { if (valid) { FT_Done_FreeType(library); font->size_cache.erase(id); } } ///////////////////////// void DynamicFont::_reload_cache() { ERR_FAIL_COND(cache_id.size < 1); if (!data.is_valid()) return; data_at_size = data->_get_dynamic_font_at_size(cache_id); for (int i = 0; i < fallbacks.size(); i++) { fallback_data_at_size[i] = fallbacks[i]->_get_dynamic_font_at_size(cache_id); } emit_changed(); _change_notify(); } void DynamicFont::set_font_data(const Ref &p_data) { data = p_data; if (data.is_valid()) data_at_size = data->_get_dynamic_font_at_size(cache_id); else data_at_size = Ref(); emit_changed(); } Ref DynamicFont::get_font_data() const { return data; } void DynamicFont::set_size(int p_size) { if (cache_id.size == p_size) return; cache_id.size = p_size; _reload_cache(); } int DynamicFont::get_size() const { return cache_id.size; } bool DynamicFont::get_use_mipmaps() const { return cache_id.mipmaps; } void DynamicFont::set_use_mipmaps(bool p_enable) { if (cache_id.mipmaps == p_enable) return; cache_id.mipmaps = p_enable; _reload_cache(); } bool DynamicFont::get_use_filter() const { return cache_id.filter; } void DynamicFont::set_use_filter(bool p_enable) { if (cache_id.filter == p_enable) return; cache_id.filter = p_enable; _reload_cache(); } int DynamicFont::get_spacing(int p_type) const { if (p_type == SPACING_TOP) { return spacing_top; } else if (p_type == SPACING_BOTTOM) { return spacing_bottom; } else if (p_type == SPACING_CHAR) { return spacing_char; } else if (p_type == SPACING_SPACE) { return spacing_space; } return 0; } void DynamicFont::set_spacing(int p_type, int p_value) { if (p_type == SPACING_TOP) { spacing_top = p_value; } else if (p_type == SPACING_BOTTOM) { spacing_bottom = p_value; } else if (p_type == SPACING_CHAR) { spacing_char = p_value; } else if (p_type == SPACING_SPACE) { spacing_space = p_value; } emit_changed(); _change_notify(); } float DynamicFont::get_height() const { if (!data_at_size.is_valid()) return 1; return data_at_size->get_height() + spacing_top + spacing_bottom; } float DynamicFont::get_ascent() const { if (!data_at_size.is_valid()) return 1; return data_at_size->get_ascent() + spacing_top; } float DynamicFont::get_descent() const { if (!data_at_size.is_valid()) return 1; return data_at_size->get_descent() + spacing_bottom; } Size2 DynamicFont::get_char_size(CharType p_char, CharType p_next) const { if (!data_at_size.is_valid()) return Size2(1, 1); Size2 ret = data_at_size->get_char_size(p_char, p_next, fallback_data_at_size); if (p_char == ' ') ret.width += spacing_space + spacing_char; else if (p_next) ret.width += spacing_char; return ret; } bool DynamicFont::is_distance_field_hint() const { return false; } float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate) const { if (!data_at_size.is_valid()) return 0; return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, fallback_data_at_size) + spacing_char; } void DynamicFont::set_fallback(int p_idx, const Ref &p_data) { ERR_FAIL_COND(p_data.is_null()); ERR_FAIL_INDEX(p_idx, fallbacks.size()); fallbacks[p_idx] = p_data; fallback_data_at_size[p_idx] = fallbacks[p_idx]->_get_dynamic_font_at_size(cache_id); } void DynamicFont::add_fallback(const Ref &p_data) { ERR_FAIL_COND(p_data.is_null()); fallbacks.push_back(p_data); fallback_data_at_size.push_back(fallbacks[fallbacks.size() - 1]->_get_dynamic_font_at_size(cache_id)); //const.. _change_notify(); emit_changed(); _change_notify(); } int DynamicFont::get_fallback_count() const { return fallbacks.size(); } Ref DynamicFont::get_fallback(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, fallbacks.size(), Ref()); return fallbacks[p_idx]; } void DynamicFont::remove_fallback(int p_idx) { ERR_FAIL_INDEX(p_idx, fallbacks.size()); fallbacks.remove(p_idx); fallback_data_at_size.remove(p_idx); emit_changed(); _change_notify(); } bool DynamicFont::_set(const StringName &p_name, const Variant &p_value) { String str = p_name; if (str.begins_with("fallback/")) { int idx = str.get_slicec('/', 1).to_int(); Ref fd = p_value; if (fd.is_valid()) { if (idx == fallbacks.size()) { add_fallback(fd); return true; } else if (idx >= 0 && idx < fallbacks.size()) { set_fallback(idx, fd); return true; } else { return false; } } else if (idx >= 0 && idx < fallbacks.size()) { remove_fallback(idx); return true; } } return false; } bool DynamicFont::_get(const StringName &p_name, Variant &r_ret) const { String str = p_name; if (str.begins_with("fallback/")) { int idx = str.get_slicec('/', 1).to_int(); if (idx == fallbacks.size()) { r_ret = Ref(); return true; } else if (idx >= 0 && idx < fallbacks.size()) { r_ret = get_fallback(idx); return true; } } return false; } void DynamicFont::_get_property_list(List *p_list) const { for (int i = 0; i < fallbacks.size(); i++) { p_list->push_back(PropertyInfo(Variant::OBJECT, "fallback/" + itos(i), PROPERTY_HINT_RESOURCE_TYPE, "DynamicFontData")); } p_list->push_back(PropertyInfo(Variant::OBJECT, "fallback/" + itos(fallbacks.size()), PROPERTY_HINT_RESOURCE_TYPE, "DynamicFontData")); } void DynamicFont::_bind_methods() { ObjectTypeDB::bind_method(_MD("set_font_data", "data:DynamicFontData"), &DynamicFont::set_font_data); ObjectTypeDB::bind_method(_MD("get_font_data:DynamicFontData"), &DynamicFont::get_font_data); ObjectTypeDB::bind_method(_MD("set_size", "data"), &DynamicFont::set_size); ObjectTypeDB::bind_method(_MD("get_size"), &DynamicFont::get_size); ObjectTypeDB::bind_method(_MD("set_use_mipmaps", "enable"), &DynamicFont::set_use_mipmaps); ObjectTypeDB::bind_method(_MD("get_use_mipmaps"), &DynamicFont::get_use_mipmaps); ObjectTypeDB::bind_method(_MD("set_use_filter", "enable"), &DynamicFont::set_use_filter); ObjectTypeDB::bind_method(_MD("get_use_filter"), &DynamicFont::get_use_filter); ObjectTypeDB::bind_method(_MD("set_spacing", "type", "value"), &DynamicFont::set_spacing); ObjectTypeDB::bind_method(_MD("get_spacing", "type"), &DynamicFont::get_spacing); ObjectTypeDB::bind_method(_MD("add_fallback", "data:DynamicFontData"), &DynamicFont::add_fallback); ObjectTypeDB::bind_method(_MD("set_fallback", "idx", "data:DynamicFontData"), &DynamicFont::set_fallback); ObjectTypeDB::bind_method(_MD("get_fallback:DynamicFontData", "idx"), &DynamicFont::get_fallback); ObjectTypeDB::bind_method(_MD("remove_fallback", "idx"), &DynamicFont::remove_fallback); ObjectTypeDB::bind_method(_MD("get_fallback_count"), &DynamicFont::get_fallback_count); ADD_PROPERTY(PropertyInfo(Variant::INT, "font/size"), _SCS("set_size"), _SCS("get_size")); ADD_PROPERTYINZ(PropertyInfo(Variant::INT, "extra_spacing/top"), _SCS("set_spacing"), _SCS("get_spacing"), SPACING_TOP); ADD_PROPERTYINZ(PropertyInfo(Variant::INT, "extra_spacing/bottom"), _SCS("set_spacing"), _SCS("get_spacing"), SPACING_BOTTOM); ADD_PROPERTYINZ(PropertyInfo(Variant::INT, "extra_spacing/char"), _SCS("set_spacing"), _SCS("get_spacing"), SPACING_CHAR); ADD_PROPERTYINZ(PropertyInfo(Variant::INT, "extra_spacing/space"), _SCS("set_spacing"), _SCS("get_spacing"), SPACING_SPACE); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "font/use_mipmaps"), _SCS("set_use_mipmaps"), _SCS("get_use_mipmaps")); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "font/use_filter"), _SCS("set_use_filter"), _SCS("get_use_filter")); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "font/font", PROPERTY_HINT_RESOURCE_TYPE, "DynamicFontData"), _SCS("set_font_data"), _SCS("get_font_data")); BIND_CONSTANT(SPACING_TOP); BIND_CONSTANT(SPACING_BOTTOM); BIND_CONSTANT(SPACING_CHAR); BIND_CONSTANT(SPACING_SPACE); } DynamicFont::DynamicFont() { spacing_top = 0; spacing_bottom = 0; spacing_char = 0; spacing_space = 0; } DynamicFont::~DynamicFont() { } ///////////////////////// RES ResourceFormatLoaderDynamicFont::load(const String &p_path, const String &p_original_path, Error *r_error) { if (r_error) *r_error = ERR_FILE_CANT_OPEN; Ref dfont; dfont.instance(); dfont->set_font_path(p_path); if (r_error) *r_error = OK; return dfont; } void ResourceFormatLoaderDynamicFont::get_recognized_extensions(List *p_extensions) const { p_extensions->push_back("ttf"); p_extensions->push_back("otf"); } bool ResourceFormatLoaderDynamicFont::handles_type(const String &p_type) const { return (p_type == "DynamicFontData"); } String ResourceFormatLoaderDynamicFont::get_resource_type(const String &p_path) const { String el = p_path.extension().to_lower(); if (el == "ttf" || el == "otf") return "DynamicFontData"; return ""; } #endif