Perfect FreeType-based outlines for DynamicFonts

- Implement outlines based on FreeType Stroker API. This allows
  artifact-free results, similar to what you will see in Web or any text
  editing tools. Outline is a part of DynamicFont rather than Label,
  because outlines have to be baked into the font's atlas. Font has a
  default outline_color and a Label can specify font_outline_modulator
  that will be multiplied with the Font's color to get the final result.

- draw_char now has to be called twice to fully render a text - first
  with p_outline == true for each character and then with
  p_outline == false for each character.

- Number of draw-calls is reduced from 5 to 2 per outlined character.

- Overall cleanup of DynamicFont code, extracted duplicated code pieces
  into separate methods.

- The change is backward-compatible - Labels still have outline
  properties that work exactly as they worked before.

Closes #16279.
This commit is contained in:
Ruslan Mustakov 2018-03-17 15:44:34 +07:00
parent 6e8e401359
commit 5cd12f6649
13 changed files with 425 additions and 295 deletions

View file

@ -87,6 +87,7 @@ void OutputStrings::_notification(int p_what) {
float h_ofs = (int)h_scroll->get_value();
Point2 icon_ofs = Point2(0, (font_height - (int)icon_error->get_height()) / 2);
FontDrawer drawer(font, Color(1, 1, 1));
while (E && ofs.y < (size_height - (int)margin.y)) {
String str = E->get().text;

View file

@ -1121,6 +1121,7 @@ void ItemList::_notification(int p_what) {
text_ofs += base_ofs;
text_ofs += items[i].rect_cache.position;
FontDrawer drawer(font, Color(1, 1, 1));
for (int j = 0; j < ss; j++) {
if (j == line_limit_cache[line]) {
@ -1129,7 +1130,7 @@ void ItemList::_notification(int p_what) {
if (line >= max_text_lines)
break;
}
ofs += font->draw_char(get_canvas_item(), text_ofs + Vector2(ofs + (max_len - line_size_cache[line]) / 2, line * (font_height + line_separation)).floor(), items[i].text[j], items[i].text[j + 1], modulate);
ofs += drawer.draw_char(get_canvas_item(), text_ofs + Vector2(ofs + (max_len - line_size_cache[line]) / 2, line * (font_height + line_separation)).floor(), items[i].text[j], items[i].text[j + 1], modulate);
}
//special multiline mode

View file

@ -93,6 +93,7 @@ void Label::_notification(int p_what) {
bool use_outline = get_constant("shadow_as_outline");
Point2 shadow_ofs(get_constant("shadow_offset_x"), get_constant("shadow_offset_y"));
int line_spacing = get_constant("line_spacing");
Color font_outline_modulator = get_color("font_outline_modulator");
style->draw(ci, Rect2(Point2(0, 0), get_size()));
@ -150,6 +151,7 @@ void Label::_notification(int p_what) {
int line = 0;
int line_to = lines_skipped + (lines_visible > 0 ? lines_visible : 1);
FontDrawer drawer(font, font_outline_modulator);
while (wc) {
/* handle lines not meant to be drawn quickly */
if (line >= line_to)
@ -244,11 +246,11 @@ void Label::_notification(int p_what) {
n = String::char_uppercase(c);
}
float move = font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + shadow_ofs, c, n, font_color_shadow);
float move = font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + shadow_ofs, c, n, font_color_shadow, false);
if (use_outline) {
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, shadow_ofs.y), c, n, font_color_shadow);
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow);
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow);
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, shadow_ofs.y), c, n, font_color_shadow, false);
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow, false);
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow, false);
}
x_ofs_shadow += move;
chars_total_shadow++;
@ -265,7 +267,7 @@ void Label::_notification(int p_what) {
n = String::char_uppercase(c);
}
x_ofs += font->draw_char(ci, Point2(x_ofs, y_ofs), c, n, font_color);
x_ofs += drawer.draw_char(ci, Point2(x_ofs, y_ofs), c, n, font_color);
chars_total++;
}
}

View file

@ -656,6 +656,7 @@ void LineEdit::_notification(int p_what) {
}
int caret_height = font->get_height() > y_area ? y_area : font->get_height();
FontDrawer drawer(font, Color(1, 1, 1));
while (true) {
//end of string, break!
@ -683,7 +684,7 @@ void LineEdit::_notification(int p_what) {
VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 1)), font_color);
}
font->draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color);
drawer.draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color);
x_ofs += im_char_width;
ofs++;
@ -704,7 +705,7 @@ void LineEdit::_notification(int p_what) {
if (selected)
VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs), Size2(char_width, caret_height)), selection_color);
font->draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, selected ? font_color_selected : font_color);
drawer.draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, selected ? font_color_selected : font_color);
if (char_ofs == cursor_pos && draw_caret) {
if (ime_text.length() == 0) {
@ -737,7 +738,7 @@ void LineEdit::_notification(int p_what) {
VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 1)), font_color);
}
font->draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color);
drawer.draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color);
x_ofs += im_char_width;
ofs++;

View file

@ -286,6 +286,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
}
rchar = 0;
FontDrawer drawer(font, Color(1, 1, 1));
while (*c) {
int end = 0;
@ -395,9 +396,9 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
}
if (selected) {
font->draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), c[i], c[i + 1], override_selected_font_color ? selection_fg : color);
drawer.draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), c[i], c[i + 1], override_selected_font_color ? selection_fg : color);
} else {
cw = font->draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), c[i], c[i + 1], color);
cw = drawer.draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), c[i], c[i + 1], color);
}
}

View file

@ -782,6 +782,7 @@ void TextEdit::_notification(int p_what) {
int line = cursor.line_ofs - 1;
// another row may be visible during smooth scrolling
int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0);
FontDrawer drawer(cache.font, Color(1, 1, 1));
for (int i = 0; i < draw_amount; i++) {
line++;
@ -1040,7 +1041,7 @@ void TextEdit::_notification(int p_what) {
if (brace_open_mismatch)
color = cache.brace_mismatch_color;
cache.font->draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color);
drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color);
}
if (
@ -1049,7 +1050,7 @@ void TextEdit::_notification(int p_what) {
if (brace_close_mismatch)
color = cache.brace_mismatch_color;
cache.font->draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color);
drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color);
}
}
@ -1082,7 +1083,7 @@ void TextEdit::_notification(int p_what) {
VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color);
}
cache.font->draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color);
drawer.draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color);
char_ofs += im_char_width;
ofs++;
@ -1109,7 +1110,7 @@ void TextEdit::_notification(int p_what) {
}
if (str[j] >= 32) {
int w = cache.font->draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color);
int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color);
if (underlined) {
draw_rect(Rect2(char_ofs + char_margin + ofs_x, ofs_y + ascent + 2, w, 1), in_selection && override_selected_font_color ? cache.font_selected_color : color);
}
@ -1158,7 +1159,7 @@ void TextEdit::_notification(int p_what) {
VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color);
}
cache.font->draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color);
drawer.draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color);
char_ofs += im_char_width;
ofs++;

View file

@ -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);

View file

@ -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,53 +259,54 @@ 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());
}
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);
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;
ret.x += (delta.x >> 6) / oversampling;
break;
}
} else {
ret.x += (delta.x >> 6) / oversampling;
}
FT_Get_Kerning(font->face, p_char, p_next, FT_KERNING_DEFAULT, &delta);
advance = (delta.x / 64.0) / oversampling;
}
return advance;
}
Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const {
if (!valid)
return Size2(1, 1);
const_cast<DynamicFontAtSize *>(this)->_update_char(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;
ERR_FAIL_COND_V(!ch, Size2());
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);
return ret;
@ -324,101 +322,41 @@ 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) {
float advance = 0.0;
//not found, try in fallbacks
bool used_fallback = false;
if (ch->found) {
ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= font->textures.size(), 0);
for (int i = 0; i < p_fallbacks.size(); i++) {
DynamicFontAtSize *fb = const_cast<DynamicFontAtSize *>(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;
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());
advance = ch->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<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 += _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) {
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;
}
if (char_map.has(p_char))
return;
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;
_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);
}
//find a texture to fit this...
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;
}
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;
}
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;
}
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;
}
if (!data_at_size.is_valid())
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 (!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);

View file

@ -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;

View file

@ -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() {

View file

@ -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();

View file

@ -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;
}

View file

@ -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();