diff options
Diffstat (limited to 'scene/resources')
-rw-r--r-- | scene/resources/default_theme/default_theme.cpp | 1 | ||||
-rw-r--r-- | scene/resources/dynamic_font.cpp | 517 | ||||
-rw-r--r-- | scene/resources/dynamic_font.h | 35 | ||||
-rw-r--r-- | scene/resources/dynamic_font_stb.cpp | 22 | ||||
-rw-r--r-- | scene/resources/dynamic_font_stb.h | 4 | ||||
-rw-r--r-- | scene/resources/font.cpp | 43 | ||||
-rw-r--r-- | scene/resources/font.h | 49 |
7 files changed, 397 insertions, 274 deletions
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 2e652a00f9..5b9ebbd4e9 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -415,6 +415,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_color", "Label", Color(1, 1, 1)); theme->set_color("font_color_shadow", "Label", Color(0, 0, 0, 0)); + theme->set_color("font_outline_modulator", "Label", Color(1, 1, 1)); theme->set_constant("shadow_offset_x", "Label", 1 * scale); theme->set_constant("shadow_offset_y", "Label", 1 * scale); diff --git a/scene/resources/dynamic_font.cpp b/scene/resources/dynamic_font.cpp index 3d825b5a27..0f8db44596 100644 --- a/scene/resources/dynamic_font.cpp +++ b/scene/resources/dynamic_font.cpp @@ -33,8 +33,12 @@ #include "os/file_access.h" #include "os/os.h" -bool DynamicFontData::CacheID::operator<(CacheID right) const { +#include FT_STROKER_H + +#define __STDC_LIMIT_MACROS +#include <stdint.h> +bool DynamicFontData::CacheID::operator<(CacheID right) const { return key < right.key; } @@ -212,8 +216,8 @@ Error DynamicFontAtSize::_load() { error = FT_Set_Pixel_Sizes(face, 0, id.size * oversampling); } - ascent = (face->size->metrics.ascender >> 6) / oversampling * scale_color_font; - descent = (-face->size->metrics.descender >> 6) / oversampling * scale_color_font; + ascent = (face->size->metrics.ascender / 64.0) / oversampling * scale_color_font; + descent = (-face->size->metrics.descender / 64.0) / oversampling * scale_color_font; linegap = 0; texture_flags = 0; if (id.mipmaps) @@ -241,18 +245,11 @@ float DynamicFontAtSize::get_descent() const { return descent; } -Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const { +const Pair<const DynamicFontAtSize::Character *, DynamicFontAtSize *> DynamicFontAtSize::_find_char_with_font(CharType p_char, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const { + const Character *chr = char_map.getptr(p_char); + ERR_FAIL_COND_V(!chr, (Pair<const Character *, DynamicFontAtSize *>(NULL, NULL))); - if (!valid) - return Size2(1, 1); - const_cast<DynamicFontAtSize *>(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) { + if (!chr->found) { //not found, try in fallbacks for (int i = 0; i < p_fallbacks.size(); i++) { @@ -262,52 +259,53 @@ Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next, const V continue; fb->_update_char(p_char); - const Character *ch = fb->char_map.getptr(p_char); - ERR_CONTINUE(!ch); + const Character *fallback_chr = fb->char_map.getptr(p_char); + ERR_CONTINUE(!fallback_chr); - if (!ch->found) + if (!fallback_chr->found) continue; - c = ch; - break; + return Pair<const Character *, DynamicFontAtSize *>(fallback_chr, fb); } - //not found, try 0xFFFD to display 'not found'. - if (!c->found) { - - const_cast<DynamicFontAtSize *>(this)->_update_char(0xFFFD); - c = char_map.getptr(0xFFFD); - ERR_FAIL_COND_V(!c, Size2()); - } + //not found, try 0xFFFD to display 'not found'. + const_cast<DynamicFontAtSize *>(this)->_update_char(0xFFFD); + chr = char_map.getptr(0xFFFD); + ERR_FAIL_COND_V(!chr, (Pair<const Character *, DynamicFontAtSize *>(NULL, NULL))); } - if (c->found) { - ret.x = c->advance; - } + return Pair<const Character *, DynamicFontAtSize *>(chr, const_cast<DynamicFontAtSize *>(this)); +} + +float DynamicFontAtSize::_get_kerning_advance(const DynamicFontAtSize *font, CharType p_char, CharType p_next) const { + float advance = 0.0; if (p_next) { FT_Vector delta; - FT_Get_Kerning(face, p_char, p_next, FT_KERNING_DEFAULT, &delta); + FT_Get_Kerning(font->face, p_char, p_next, FT_KERNING_DEFAULT, &delta); + advance = (delta.x / 64.0) / oversampling; + } - if (delta.x == 0) { - for (int i = 0; i < p_fallbacks.size(); i++) { + return advance; +} - DynamicFontAtSize *fb = const_cast<DynamicFontAtSize *>(p_fallbacks[i].ptr()); - if (!fb->valid) - continue; +Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const { - FT_Get_Kerning(fb->face, p_char, p_next, FT_KERNING_DEFAULT, &delta); + if (!valid) + return Size2(1, 1); + const_cast<DynamicFontAtSize *>(this)->_update_char(p_char); - if (delta.x == 0) - continue; + Pair<const Character *, DynamicFontAtSize *> char_pair_with_font = _find_char_with_font(p_char, p_fallbacks); + const Character *ch = char_pair_with_font.first; + DynamicFontAtSize *font = char_pair_with_font.second; + ERR_FAIL_COND_V(!ch, Size2()); - ret.x += (delta.x >> 6) / oversampling; - break; - } - } else { - ret.x += (delta.x >> 6) / oversampling; - } + Size2 ret(0, get_height()); + + if (ch->found) { + ret.x = ch->advance; } + ret.x += _get_kerning_advance(font, p_char, p_next); // ensures oversampled glyphs will have enough space when this value is used by clipping/wrapping algorithms ret.x = Math::ceil(ret.x); @@ -324,102 +322,42 @@ void DynamicFontAtSize::set_texture_flags(uint32_t 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<Ref<DynamicFontAtSize> > &p_fallbacks) const { +float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks, bool p_advance_only) const { if (!valid) return 0; const_cast<DynamicFontAtSize *>(this)->_update_char(p_char); - const Character *c = char_map.getptr(p_char); + Pair<const Character *, DynamicFontAtSize *> char_pair_with_font = _find_char_with_font(p_char, p_fallbacks); + const Character *ch = char_pair_with_font.first; + DynamicFontAtSize *font = char_pair_with_font.second; - float advance = 0; + ERR_FAIL_COND_V(!ch, 0.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<DynamicFontAtSize *>(p_fallbacks[i].ptr()); - if (!fb->valid) - continue; + float advance = 0.0; - fb->_update_char(p_char); - const Character *ch = fb->char_map.getptr(p_char); - ERR_CONTINUE(!ch); + if (ch->found) { + ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= font->textures.size(), 0); - if (!ch->found) - continue; + if (!p_advance_only && ch->texture_idx != -1) { Point2 cpos = p_pos; cpos.x += ch->h_align; - cpos.y -= fb->get_ascent(); + cpos.y -= font->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) { - Color modulate = p_modulate; - if (FT_HAS_COLOR(fb->face)) { - modulate.r = modulate.g = modulate.b = 1; - } - VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, ch->rect.size * Vector2(fb->scale_color_font, fb->scale_color_font)), fb->textures[ch->texture_idx].texture->get_rid(), ch->rect_uv, modulate, false, RID(), false); - } - advance = ch->advance; - used_fallback = true; - break; - } - //not found, try 0xFFFD to display 'not found'. - - if (!used_fallback) { - - const_cast<DynamicFontAtSize *>(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) { Color modulate = p_modulate; if (FT_HAS_COLOR(face)) { - modulate.r = modulate.g = modulate.b = 1; + modulate.r = modulate.g = modulate.b = 1.0; } - VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size * Vector2(scale_color_font, scale_color_font)), textures[c->texture_idx].texture->get_rid(), c->rect_uv, modulate, false, RID(), false); + RID texture = font->textures[ch->texture_idx].texture->get_rid(); + VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, ch->rect.size * Vector2(font->scale_color_font, font->scale_color_font)), texture, ch->rect_uv, modulate, false, RID(), false); } - 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<DynamicFontAtSize *>(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) / oversampling; - break; - } - } else { - advance += (delta.x >> 6) / oversampling; - } + advance = ch->advance; } + advance += _get_kerning_advance(font, p_char, p_next); + return advance; } @@ -443,96 +381,37 @@ void DynamicFontAtSize::_ft_stream_close(FT_Stream stream) { 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 ft_hinting; - - switch (font->hinting) { - case DynamicFontData::HINTING_NONE: - ft_hinting = FT_LOAD_NO_HINTING; - break; - case DynamicFontData::HINTING_LIGHT: - ft_hinting = FT_LOAD_TARGET_LIGHT; - break; - default: - ft_hinting = FT_LOAD_TARGET_NORMAL; - break; - } - - int error = FT_Load_Char(face, p_char, FT_HAS_COLOR(face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting); - if (!error) { - error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); - } - if (error) { - - int advance = 0; - 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 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); - } +DynamicFontAtSize::Character DynamicFontAtSize::Character::not_found() { + Character ch; + ch.texture_idx = -1; + ch.advance = 0; + ch.h_align = 0; + ch.v_align = 0; + ch.found = false; + return ch; +} - //find a texture to fit this... +DynamicFontAtSize::TexturePosition DynamicFontAtSize::_find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height) { + TexturePosition ret; + ret.index = -1; + ret.x = 0; + ret.y = 0; - int color_size = slot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2; - Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8; - int tex_index = -1; - int tex_x = 0; - int tex_y = 0; + int mw = p_width; + int mh = p_height; for (int i = 0; i < textures.size(); i++) { CharTexture &ct = textures[i]; - if (ct.texture->get_format() != require_format) + if (ct.texture->get_format() != p_image_format) continue; if (mw > ct.texture_size || mh > ct.texture_size) //too big for this texture continue; - tex_y = 0x7FFFFFFF; - tex_x = 0; + ret.y = 0x7FFFFFFF; + ret.x = 0; for (int j = 0; j < ct.texture_size - mw; j++) { @@ -545,23 +424,25 @@ void DynamicFontAtSize::_update_char(CharType p_char) { max_y = y; } - if (max_y < tex_y) { - tex_y = max_y; - tex_x = j; + if (max_y < ret.y) { + ret.y = max_y; + ret.x = j; } } - if (tex_y == 0x7FFFFFFF || tex_y + mh > ct.texture_size) + if (ret.y == 0x7FFFFFFF || ret.y + mh > ct.texture_size) continue; //fail, could not fit it here - tex_index = i; + ret.index = i; break; } - if (tex_index == -1) { + //print_line("CHAR: "+String::chr(p_char)+" TEX INDEX: "+itos(tex_index)+" X: "+itos(tex_x)+" Y: "+itos(tex_y)); + + if (ret.index == -1) { //could not find texture to fit, create one - tex_x = 0; - tex_y = 0; + ret.x = 0; + ret.y = 0; int texsize = MAX(id.size * 8, 256); if (mw > texsize) @@ -575,13 +456,13 @@ void DynamicFontAtSize::_update_char(CharType p_char) { CharTexture tex; tex.texture_size = texsize; - tex.imgdata.resize(texsize * texsize * color_size); + tex.imgdata.resize(texsize * texsize * p_color_size); //grayscale alpha { //zero texture PoolVector<uint8_t>::Write w = tex.imgdata.write(); - ERR_FAIL_COND(texsize * texsize * color_size > tex.imgdata.size()); - for (int i = 0; i < texsize * texsize * color_size; i++) { + ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.imgdata.size(), ret); + for (int i = 0; i < texsize * texsize * p_color_size; i++) { w[i] = 0; } } @@ -590,12 +471,31 @@ void DynamicFontAtSize::_update_char(CharType p_char) { tex.offsets[i] = 0; textures.push_back(tex); - tex_index = textures.size() - 1; + ret.index = textures.size() - 1; } + return ret; +} + +DynamicFontAtSize::Character DynamicFontAtSize::_bitmap_to_character(FT_Bitmap bitmap, int yofs, int xofs, float advance) { + int w = bitmap.width; + int h = bitmap.rows; + + int mw = w + rect_margin * 2; + int mh = h + rect_margin * 2; + + ERR_FAIL_COND_V(mw > 4096, Character::not_found()); + ERR_FAIL_COND_V(mh > 4096, Character::not_found()); + + int color_size = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2; + Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8; + + TexturePosition tex_pos = _find_texture_pos_for_glyph(color_size, require_format, mw, mh); + ERR_FAIL_COND_V(tex_pos.index < 0, Character::not_found()); + //fit character in char texture - CharTexture &tex = textures[tex_index]; + CharTexture &tex = textures[tex_pos.index]; { PoolVector<uint8_t>::Write wr = tex.imgdata.write(); @@ -603,30 +503,30 @@ void DynamicFontAtSize::_update_char(CharType p_char) { 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) * color_size; - ERR_FAIL_COND(ofs >= tex.imgdata.size()); - switch (slot->bitmap.pixel_mode) { + int ofs = ((i + tex_pos.y + rect_margin) * tex.texture_size + j + tex_pos.x + rect_margin) * color_size; + ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), Character::not_found()); + switch (bitmap.pixel_mode) { case FT_PIXEL_MODE_MONO: { - int byte = i * slot->bitmap.pitch + (j >> 3); + int byte = i * bitmap.pitch + (j >> 3); int bit = 1 << (7 - (j % 8)); wr[ofs + 0] = 255; //grayscale as 1 - wr[ofs + 1] = slot->bitmap.buffer[byte] & bit ? 255 : 0; + wr[ofs + 1] = bitmap.buffer[byte] & bit ? 255 : 0; } break; case FT_PIXEL_MODE_GRAY: wr[ofs + 0] = 255; //grayscale as 1 - wr[ofs + 1] = slot->bitmap.buffer[i * slot->bitmap.pitch + j]; + wr[ofs + 1] = bitmap.buffer[i * bitmap.pitch + j]; break; case FT_PIXEL_MODE_BGRA: { - int ofs_color = i * slot->bitmap.pitch + (j << 2); - wr[ofs + 2] = slot->bitmap.buffer[ofs_color + 0]; - wr[ofs + 1] = slot->bitmap.buffer[ofs_color + 1]; - wr[ofs + 0] = slot->bitmap.buffer[ofs_color + 2]; - wr[ofs + 3] = slot->bitmap.buffer[ofs_color + 3]; + int ofs_color = i * bitmap.pitch + (j << 2); + wr[ofs + 2] = bitmap.buffer[ofs_color + 0]; + wr[ofs + 1] = bitmap.buffer[ofs_color + 1]; + wr[ofs + 0] = bitmap.buffer[ofs_color + 2]; + wr[ofs + 3] = bitmap.buffer[ofs_color + 3]; } break; // TODO: FT_PIXEL_MODE_LCD default: - ERR_EXPLAIN("Font uses unsupported pixel format: " + itos(slot->bitmap.pixel_mode)); - ERR_FAIL(); + ERR_EXPLAIN("Font uses unsupported pixel format: " + itos(bitmap.pixel_mode)); + ERR_FAIL_V(Character::not_found()); break; } } @@ -648,24 +548,100 @@ void DynamicFontAtSize::_update_char(CharType p_char) { // update height array - for (int k = tex_x; k < tex_x + mw; k++) { - - tex.offsets[k] = tex_y + mh; + for (int k = tex_pos.x; k < tex_pos.x + mw; k++) { + tex.offsets[k] = tex_pos.y + mh; } Character chr; chr.h_align = xofs * scale_color_font / oversampling; chr.v_align = ascent - (yofs * scale_color_font / oversampling); // + ascent - descent; chr.advance = advance * scale_color_font / oversampling; - chr.texture_idx = tex_index; + chr.texture_idx = tex_pos.index; chr.found = true; - chr.rect_uv = Rect2(tex_x + rect_margin, tex_y + rect_margin, w, h); + chr.rect_uv = Rect2(tex_pos.x + rect_margin, tex_pos.y + rect_margin, w, h); chr.rect = chr.rect_uv; chr.rect.position /= oversampling; - chr.rect.size /= oversampling; + chr.rect.size = chr.rect.size * scale_color_font / oversampling; + return chr; +} + +DynamicFontAtSize::Character DynamicFontAtSize::_make_outline_char(CharType p_char) { + Character ret = Character::not_found(); + + if (FT_Load_Char(face, p_char, FT_LOAD_NO_BITMAP | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)) != 0) + return ret; + + FT_Stroker stroker; + if (FT_Stroker_New(library, &stroker) != 0) + return ret; + + FT_Stroker_Set(stroker, (int)(id.outline_size * oversampling * 64.0), FT_STROKER_LINECAP_BUTT, FT_STROKER_LINEJOIN_ROUND, 0); + FT_Glyph glyph; + FT_BitmapGlyph glyph_bitmap; + + if (FT_Get_Glyph(face->glyph, &glyph) != 0) + goto cleanup_stroker; + if (FT_Glyph_Stroke(&glyph, stroker, 1) != 0) + goto cleanup_glyph; + if (FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1) != 0) + goto cleanup_glyph; + + glyph_bitmap = (FT_BitmapGlyph)glyph; + ret = _bitmap_to_character(glyph_bitmap->bitmap, glyph_bitmap->top, glyph_bitmap->left, glyph->advance.x / 65536.0); + +cleanup_glyph: + FT_Done_Glyph(glyph); +cleanup_stroker: + FT_Stroker_Done(stroker); + return ret; +} + +void DynamicFontAtSize::_update_char(CharType p_char) { + + if (char_map.has(p_char)) + return; + + _THREAD_SAFE_METHOD_ + + Character character = Character::not_found(); + + FT_GlyphSlot slot = face->glyph; + + if (FT_Get_Char_Index(face, p_char) == 0) { + char_map[p_char] = character; + return; + } + + int ft_hinting; + + switch (font->hinting) { + case DynamicFontData::HINTING_NONE: + ft_hinting = FT_LOAD_NO_HINTING; + break; + case DynamicFontData::HINTING_LIGHT: + ft_hinting = FT_LOAD_TARGET_LIGHT; + break; + default: + ft_hinting = FT_LOAD_TARGET_NORMAL; + break; + } + + int error = FT_Load_Char(face, p_char, FT_HAS_COLOR(face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); + if (error) { + char_map[p_char] = character; + return; + } + + if (id.outline_size > 0) { + character = _make_outline_char(p_char); + } else { + error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + if (!error) + character = _bitmap_to_character(slot->bitmap, slot->bitmap_top, slot->bitmap_left, slot->advance.x / 64.0); + } - char_map[p_char] = chr; + char_map[p_char] = character; } bool DynamicFontAtSize::update_oversampling() { @@ -710,11 +686,27 @@ DynamicFontAtSize::~DynamicFontAtSize() { void DynamicFont::_reload_cache() { ERR_FAIL_COND(cache_id.size < 1); - if (!data.is_valid()) + if (!data.is_valid()) { + data_at_size.unref(); + outline_data_at_size.unref(); + fallback_data_at_size.resize(0); + fallback_outline_data_at_size.resize(0); return; + } + data_at_size = data->_get_dynamic_font_at_size(cache_id); + if (outline_cache_id.outline_size > 0) { + outline_data_at_size = data->_get_dynamic_font_at_size(outline_cache_id); + fallback_outline_data_at_size.resize(fallback_data_at_size.size()); + } else { + outline_data_at_size.unref(); + fallback_outline_data_at_size.resize(0); + } + for (int i = 0; i < fallbacks.size(); i++) { fallback_data_at_size[i] = fallbacks[i]->_get_dynamic_font_at_size(cache_id); + if (outline_cache_id.outline_size > 0) + fallback_outline_data_at_size[i] = fallbacks[i]->_get_dynamic_font_at_size(outline_cache_id); } emit_changed(); @@ -724,12 +716,10 @@ void DynamicFont::_reload_cache() { void DynamicFont::set_font_data(const Ref<DynamicFontData> &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<DynamicFontAtSize>(); + _reload_cache(); emit_changed(); + _change_notify(); } Ref<DynamicFontData> DynamicFont::get_font_data() const { @@ -742,6 +732,7 @@ void DynamicFont::set_size(int p_size) { if (cache_id.size == p_size) return; cache_id.size = p_size; + outline_cache_id.size = p_size; _reload_cache(); } @@ -750,6 +741,30 @@ int DynamicFont::get_size() const { return cache_id.size; } +void DynamicFont::set_outline_size(int p_size) { + if (outline_cache_id.outline_size == p_size) + return; + ERR_FAIL_COND(p_size < 0 || p_size > UINT8_MAX); + outline_cache_id.outline_size = p_size; + _reload_cache(); +} + +int DynamicFont::get_outline_size() const { + return outline_cache_id.outline_size; +} + +void DynamicFont::set_outline_color(Color p_color) { + if (p_color != outline_color) { + outline_color = p_color; + emit_changed(); + _change_notify(); + } +} + +Color DynamicFont::get_outline_color() const { + return outline_color; +} + bool DynamicFont::get_use_mipmaps() const { return cache_id.mipmaps; @@ -760,6 +775,7 @@ void DynamicFont::set_use_mipmaps(bool p_enable) { if (cache_id.mipmaps == p_enable) return; cache_id.mipmaps = p_enable; + outline_cache_id.mipmaps = p_enable; _reload_cache(); } @@ -773,6 +789,7 @@ void DynamicFont::set_use_filter(bool p_enable) { if (cache_id.filter == p_enable) return; cache_id.filter = p_enable; + outline_cache_id.filter = p_enable; _reload_cache(); } @@ -862,13 +879,24 @@ 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 { +bool DynamicFont::has_outline() const { + return outline_cache_id.outline_size > 0; +} + +float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const { + const Ref<DynamicFontAtSize> &font_at_size = p_outline && outline_cache_id.outline_size > 0 ? outline_data_at_size : data_at_size; - if (!data_at_size.is_valid()) + if (!font_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; + const Vector<Ref<DynamicFontAtSize> > &fallbacks = p_outline && outline_cache_id.outline_size > 0 ? fallback_outline_data_at_size : fallback_data_at_size; + Color color = p_outline && outline_cache_id.outline_size > 0 ? p_modulate * outline_color : p_modulate; + + // If requested outline draw, but no outline is present, simply return advance without drawing anything + bool advance_only = p_outline && outline_cache_id.outline_size == 0; + return font_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, color, fallbacks, advance_only) + spacing_char; } + void DynamicFont::set_fallback(int p_idx, const Ref<DynamicFontData> &p_data) { ERR_FAIL_COND(p_data.is_null()); @@ -882,6 +910,8 @@ void DynamicFont::add_fallback(const Ref<DynamicFontData> &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.. + if (outline_cache_id.outline_size > 0) + fallback_outline_data_at_size.push_back(fallbacks[fallbacks.size() - 1]->_get_dynamic_font_at_size(outline_cache_id)); _change_notify(); emit_changed(); @@ -966,6 +996,12 @@ void DynamicFont::_bind_methods() { ClassDB::bind_method(D_METHOD("set_size", "data"), &DynamicFont::set_size); ClassDB::bind_method(D_METHOD("get_size"), &DynamicFont::get_size); + ClassDB::bind_method(D_METHOD("set_outline_size", "size"), &DynamicFont::set_outline_size); + ClassDB::bind_method(D_METHOD("get_outline_size"), &DynamicFont::get_outline_size); + + ClassDB::bind_method(D_METHOD("set_outline_color", "color"), &DynamicFont::set_outline_color); + ClassDB::bind_method(D_METHOD("get_outline_color"), &DynamicFont::get_outline_color); + ClassDB::bind_method(D_METHOD("set_use_mipmaps", "enable"), &DynamicFont::set_use_mipmaps); ClassDB::bind_method(D_METHOD("get_use_mipmaps"), &DynamicFont::get_use_mipmaps); ClassDB::bind_method(D_METHOD("set_use_filter", "enable"), &DynamicFont::set_use_filter); @@ -981,6 +1017,8 @@ void DynamicFont::_bind_methods() { ADD_GROUP("Settings", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "outline_size"), "set_outline_size", "get_outline_size"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "outline_color"), "set_outline_color", "get_outline_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_mipmaps"), "set_use_mipmaps", "get_use_mipmaps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_filter"), "set_use_filter", "get_use_filter"); ADD_GROUP("Extra Spacing", "extra_spacing"); @@ -1008,6 +1046,7 @@ DynamicFont::DynamicFont() : spacing_bottom = 0; spacing_char = 0; spacing_space = 0; + outline_color = Color(1, 1, 1); if (dynamic_font_mutex) dynamic_font_mutex->lock(); dynamic_fonts.add(&font_list); diff --git a/scene/resources/dynamic_font.h b/scene/resources/dynamic_font.h index db8bd87587..f024465ccd 100644 --- a/scene/resources/dynamic_font.h +++ b/scene/resources/dynamic_font.h @@ -35,6 +35,7 @@ #include "io/resource_loader.h" #include "os/mutex.h" #include "os/thread_safe.h" +#include "pair.h" #include "scene/resources/font.h" #include <ft2build.h> @@ -49,10 +50,10 @@ class DynamicFontData : public Resource { public: struct CacheID { - union { struct { uint32_t size : 16; + uint32_t outline_size : 8; bool mipmaps : 1; bool filter : 1; }; @@ -148,8 +149,22 @@ class DynamicFontAtSize : public Reference { texture_idx = 0; v_align = 0; } + + static Character not_found(); }; + struct TexturePosition { + int index; + int x; + int y; + }; + + const Pair<const Character *, DynamicFontAtSize *> _find_char_with_font(CharType p_char, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const; + Character _make_outline_char(CharType p_char); + float _get_kerning_advance(const DynamicFontAtSize *font, CharType p_char, CharType p_next) const; + TexturePosition _find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height); + Character _bitmap_to_character(FT_Bitmap bitmap, int yofs, int xofs, float advance); + static unsigned long _ft_stream_io(FT_Stream stream, unsigned long offset, unsigned char *buffer, unsigned long count); static void _ft_stream_close(FT_Stream stream); @@ -174,7 +189,7 @@ public: Size2 get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const; - float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const; + float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks, bool p_advance_only = false) const; void set_texture_flags(uint32_t p_flags); bool update_oversampling(); @@ -200,17 +215,23 @@ public: private: Ref<DynamicFontData> data; Ref<DynamicFontAtSize> data_at_size; + Ref<DynamicFontAtSize> outline_data_at_size; Vector<Ref<DynamicFontData> > fallbacks; Vector<Ref<DynamicFontAtSize> > fallback_data_at_size; + Vector<Ref<DynamicFontAtSize> > fallback_outline_data_at_size; DynamicFontData::CacheID cache_id; + DynamicFontData::CacheID outline_cache_id; + bool valid; int spacing_top; int spacing_bottom; int spacing_char; int spacing_space; + Color outline_color; + protected: void _reload_cache(); @@ -227,6 +248,12 @@ public: void set_size(int p_size); int get_size() const; + void set_outline_size(int p_size); + int get_outline_size() const; + + void set_outline_color(Color p_color); + Color get_outline_color() const; + bool get_use_mipmaps() const; void set_use_mipmaps(bool p_enable); @@ -251,7 +278,9 @@ public: virtual bool is_distance_field_hint() const; - virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const; + virtual bool has_outline() const; + + virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const; SelfList<DynamicFont> font_list; diff --git a/scene/resources/dynamic_font_stb.cpp b/scene/resources/dynamic_font_stb.cpp index 098b794a95..29f1106d16 100644 --- a/scene/resources/dynamic_font_stb.cpp +++ b/scene/resources/dynamic_font_stb.cpp @@ -162,7 +162,7 @@ Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next) const { return ret; } -float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate) const { +float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const { const_cast<DynamicFontAtSize *>(this)->_update_char(p_char); @@ -172,13 +172,15 @@ float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharT return 0; } - 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); + if (!p_outline) { + 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); + } //textures[c->texture_idx].texture->draw(p_canvas_item,Vector2()); @@ -459,12 +461,12 @@ 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 { +float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) 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); + return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, p_outline); } DynamicFont::DynamicFont() { diff --git a/scene/resources/dynamic_font_stb.h b/scene/resources/dynamic_font_stb.h index 4c1097d28b..feae29c0c2 100644 --- a/scene/resources/dynamic_font_stb.h +++ b/scene/resources/dynamic_font_stb.h @@ -136,7 +136,7 @@ public: Size2 get_char_size(CharType p_char, CharType p_next = 0) const; - float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const; + float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const; DynamicFontAtSize(); ~DynamicFontAtSize(); @@ -171,7 +171,7 @@ public: virtual bool is_distance_field_hint() const; - virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const; + virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const; DynamicFont(); ~DynamicFont(); diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 6fc5778dd8..0bae9d9b2d 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -32,12 +32,12 @@ #include "core/io/resource_loader.h" #include "core/os/file_access.h" +#include "method_bind_ext.gen.inc" -void Font::draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate) const { - +void Font::draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate, const Color &p_outline_modulate) const { float length = get_string_size(p_text).width; if (length >= p_width) { - draw(p_canvas_item, p_pos, p_text, p_modulate, p_width); + draw(p_canvas_item, p_pos, p_text, p_modulate, p_width, p_outline_modulate); return; } @@ -56,13 +56,14 @@ void Font::draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, f ERR_PRINT("Unknown halignment type"); } break; } - draw(p_canvas_item, p_pos + Point2(ofs, 0), p_text, p_modulate, p_width); + draw(p_canvas_item, p_pos + Point2(ofs, 0), p_text, p_modulate, p_width, p_outline_modulate); } -void Font::draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate, int p_clip_w) const { - +void Font::draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate, int p_clip_w, const Color &p_outline_modulate) const { Vector2 ofs; + int chars_drawn = 0; + bool with_outline = has_outline(); for (int i = 0; i < p_text.length(); i++) { int width = get_char_size(p_text[i]).width; @@ -70,7 +71,15 @@ void Font::draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, co if (p_clip_w >= 0 && (ofs.x + width) > p_clip_w) break; //clip - ofs.x += draw_char(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], p_modulate); + ofs.x += draw_char(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], with_outline ? p_outline_modulate : p_modulate, with_outline); + ++chars_drawn; + } + + if (has_outline()) { + ofs = Vector2(0, 0); + for (int i = 0; i < chars_drawn; i++) { + ofs.x += draw_char(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], p_modulate, false); + } } } @@ -81,13 +90,14 @@ void Font::update_changes() { void Font::_bind_methods() { - ClassDB::bind_method(D_METHOD("draw", "canvas_item", "position", "string", "modulate", "clip_w"), &Font::draw, DEFVAL(Color(1, 1, 1)), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("draw", "canvas_item", "position", "string", "modulate", "clip_w", "outline_modulate"), &Font::draw, DEFVAL(Color(1, 1, 1)), DEFVAL(-1), DEFVAL(Color(1, 1, 1))); ClassDB::bind_method(D_METHOD("get_ascent"), &Font::get_ascent); ClassDB::bind_method(D_METHOD("get_descent"), &Font::get_descent); ClassDB::bind_method(D_METHOD("get_height"), &Font::get_height); ClassDB::bind_method(D_METHOD("is_distance_field_hint"), &Font::is_distance_field_hint); ClassDB::bind_method(D_METHOD("get_string_size", "string"), &Font::get_string_size); - ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "position", "char", "next", "modulate"), &Font::draw_char, DEFVAL(-1), DEFVAL(Color(1, 1, 1))); + ClassDB::bind_method(D_METHOD("has_outline"), &Font::has_outline); + ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "position", "char", "next", "modulate", "outline"), &Font::draw_char, DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(false)); ClassDB::bind_method(D_METHOD("update_changes"), &Font::update_changes); } @@ -494,23 +504,24 @@ Ref<BitmapFont> BitmapFont::get_fallback() const { return fallback; } -float BitmapFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate) const { +float BitmapFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const { const Character *c = char_map.getptr(p_char); if (!c) { if (fallback.is_valid()) - return fallback->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate); + return fallback->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, p_outline); return 0; } - Point2 cpos = p_pos; - cpos.x += c->h_align; - cpos.y -= 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) + if (!p_outline && c->texture_idx != -1) { + Point2 cpos = p_pos; + cpos.x += c->h_align; + cpos.y -= ascent; + cpos.y += c->v_align; VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size), textures[c->texture_idx]->get_rid(), c->rect, p_modulate, false, RID(), false); + } return get_char_size(p_char, p_next).width; } diff --git a/scene/resources/font.h b/scene/resources/font.h index ae08890be3..4e295b6035 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -56,14 +56,55 @@ public: virtual bool is_distance_field_hint() const = 0; - void draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate = Color(1, 1, 1), int p_clip_w = -1) const; - void draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate = Color(1, 1, 1)) const; - virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const = 0; + void draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate = Color(1, 1, 1), int p_clip_w = -1, const Color &p_outline_modulate = Color(1, 1, 1)) const; + void draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate = Color(1, 1, 1), const Color &p_outline_modulate = Color(1, 1, 1)) const; + + virtual bool has_outline() const { return false; } + virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const = 0; void update_changes(); Font(); }; +// Helper class to that draws outlines immediately and draws characters in its destructor. +class FontDrawer { + const Ref<Font> &font; + Color outline_color; + bool has_outline; + + struct PendingDraw { + RID canvas_item; + Point2 pos; + CharType chr; + CharType next; + Color modulate; + }; + + Vector<PendingDraw> pending_draws; + +public: + FontDrawer(const Ref<Font> &p_font, const Color &p_outline_color) : + font(p_font), + outline_color(p_outline_color) { + has_outline = p_font->has_outline(); + } + + float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) { + if (has_outline) { + PendingDraw draw = { p_canvas_item, p_pos, p_char, p_next, p_modulate }; + pending_draws.push_back(draw); + } + return font->draw_char(p_canvas_item, p_pos, p_char, p_next, has_outline ? outline_color : p_modulate, has_outline); + } + + ~FontDrawer() { + for (int i = 0; i < pending_draws.size(); ++i) { + const PendingDraw &draw = pending_draws[i]; + font->draw_char(draw.canvas_item, draw.pos, draw.chr, draw.next, draw.modulate, false); + } + } +}; + class BitmapFont : public Font { GDCLASS(BitmapFont, Font); @@ -153,7 +194,7 @@ public: void set_distance_field_hint(bool p_distance_field); bool is_distance_field_hint() const; - float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const; + float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const; BitmapFont(); ~BitmapFont(); |