|
|
@@ -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,25 +424,27 @@ 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);
|
|
|
+ int texsize = MAX(id.size * oversampling * 8, 256);
|
|
|
if (mw > texsize)
|
|
|
texsize = mw; //special case, adapt to it?
|
|
|
if (mh > 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,31 +548,105 @@ 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;
|
|
|
+}
|
|
|
|
|
|
- char_map[p_char] = 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;
|
|
|
}
|
|
|
|
|
|
-bool DynamicFontAtSize::update_oversampling() {
|
|
|
- if (oversampling == font_oversampling)
|
|
|
- return false;
|
|
|
- if (!valid)
|
|
|
- return false;
|
|
|
+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] = character;
|
|
|
+}
|
|
|
+
|
|
|
+void DynamicFontAtSize::update_oversampling() {
|
|
|
+ if (oversampling == font_oversampling || !valid)
|
|
|
+ return;
|
|
|
|
|
|
FT_Done_FreeType(library);
|
|
|
textures.clear();
|
|
|
@@ -680,8 +654,6 @@ bool DynamicFontAtSize::update_oversampling() {
|
|
|
oversampling = font_oversampling;
|
|
|
valid = false;
|
|
|
_load();
|
|
|
-
|
|
|
- return true;
|
|
|
}
|
|
|
|
|
|
DynamicFontAtSize::DynamicFontAtSize() {
|
|
|
@@ -710,11 +682,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 +712,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 +728,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 +737,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 +771,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 +785,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 +875,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 +906,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 +992,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 +1013,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 +1042,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);
|
|
|
@@ -1043,7 +1078,8 @@ void DynamicFont::update_oversampling() {
|
|
|
SelfList<DynamicFont> *E = dynamic_fonts.first();
|
|
|
while (E) {
|
|
|
|
|
|
- if (E->self()->data_at_size.is_valid() && E->self()->data_at_size->update_oversampling()) {
|
|
|
+ if (E->self()->data_at_size.is_valid()) {
|
|
|
+ E->self()->data_at_size->update_oversampling();
|
|
|
changed.push_back(Ref<DynamicFont>(E->self()));
|
|
|
}
|
|
|
E = E->next();
|