Merge pull request #17578 from endragor/ft-outlines
Perfect FreeType-based outlines for DynamicFonts
This commit is contained in:
commit
9dbfe5dc61
13 changed files with 434 additions and 307 deletions
|
@ -87,6 +87,7 @@ void OutputStrings::_notification(int p_what) {
|
||||||
float h_ofs = (int)h_scroll->get_value();
|
float h_ofs = (int)h_scroll->get_value();
|
||||||
Point2 icon_ofs = Point2(0, (font_height - (int)icon_error->get_height()) / 2);
|
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)) {
|
while (E && ofs.y < (size_height - (int)margin.y)) {
|
||||||
|
|
||||||
String str = E->get().text;
|
String str = E->get().text;
|
||||||
|
|
|
@ -1121,6 +1121,7 @@ void ItemList::_notification(int p_what) {
|
||||||
text_ofs += base_ofs;
|
text_ofs += base_ofs;
|
||||||
text_ofs += items[i].rect_cache.position;
|
text_ofs += items[i].rect_cache.position;
|
||||||
|
|
||||||
|
FontDrawer drawer(font, Color(1, 1, 1));
|
||||||
for (int j = 0; j < ss; j++) {
|
for (int j = 0; j < ss; j++) {
|
||||||
|
|
||||||
if (j == line_limit_cache[line]) {
|
if (j == line_limit_cache[line]) {
|
||||||
|
@ -1129,7 +1130,7 @@ void ItemList::_notification(int p_what) {
|
||||||
if (line >= max_text_lines)
|
if (line >= max_text_lines)
|
||||||
break;
|
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
|
//special multiline mode
|
||||||
|
|
|
@ -93,6 +93,7 @@ void Label::_notification(int p_what) {
|
||||||
bool use_outline = get_constant("shadow_as_outline");
|
bool use_outline = get_constant("shadow_as_outline");
|
||||||
Point2 shadow_ofs(get_constant("shadow_offset_x"), get_constant("shadow_offset_y"));
|
Point2 shadow_ofs(get_constant("shadow_offset_x"), get_constant("shadow_offset_y"));
|
||||||
int line_spacing = get_constant("line_spacing");
|
int line_spacing = get_constant("line_spacing");
|
||||||
|
Color font_outline_modulate = get_color("font_outline_modulate");
|
||||||
|
|
||||||
style->draw(ci, Rect2(Point2(0, 0), get_size()));
|
style->draw(ci, Rect2(Point2(0, 0), get_size()));
|
||||||
|
|
||||||
|
@ -150,6 +151,7 @@ void Label::_notification(int p_what) {
|
||||||
|
|
||||||
int line = 0;
|
int line = 0;
|
||||||
int line_to = lines_skipped + (lines_visible > 0 ? lines_visible : 1);
|
int line_to = lines_skipped + (lines_visible > 0 ? lines_visible : 1);
|
||||||
|
FontDrawer drawer(font, font_outline_modulate);
|
||||||
while (wc) {
|
while (wc) {
|
||||||
/* handle lines not meant to be drawn quickly */
|
/* handle lines not meant to be drawn quickly */
|
||||||
if (line >= line_to)
|
if (line >= line_to)
|
||||||
|
@ -244,11 +246,11 @@ void Label::_notification(int p_what) {
|
||||||
n = String::char_uppercase(c);
|
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) {
|
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, false);
|
||||||
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);
|
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;
|
x_ofs_shadow += move;
|
||||||
chars_total_shadow++;
|
chars_total_shadow++;
|
||||||
|
@ -265,7 +267,7 @@ void Label::_notification(int p_what) {
|
||||||
n = String::char_uppercase(c);
|
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++;
|
chars_total++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -656,6 +656,7 @@ void LineEdit::_notification(int p_what) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int caret_height = font->get_height() > y_area ? y_area : font->get_height();
|
int caret_height = font->get_height() > y_area ? y_area : font->get_height();
|
||||||
|
FontDrawer drawer(font, Color(1, 1, 1));
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
//end of string, break!
|
//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);
|
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;
|
x_ofs += im_char_width;
|
||||||
ofs++;
|
ofs++;
|
||||||
|
@ -704,7 +705,7 @@ void LineEdit::_notification(int p_what) {
|
||||||
if (selected)
|
if (selected)
|
||||||
VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs), Size2(char_width, caret_height)), selection_color);
|
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 (char_ofs == cursor_pos && draw_caret) {
|
||||||
if (ime_text.length() == 0) {
|
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);
|
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;
|
x_ofs += im_char_width;
|
||||||
ofs++;
|
ofs++;
|
||||||
|
|
|
@ -286,6 +286,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
|
||||||
}
|
}
|
||||||
|
|
||||||
rchar = 0;
|
rchar = 0;
|
||||||
|
FontDrawer drawer(font, Color(1, 1, 1));
|
||||||
while (*c) {
|
while (*c) {
|
||||||
|
|
||||||
int end = 0;
|
int end = 0;
|
||||||
|
@ -395,9 +396,9 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selected) {
|
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 {
|
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -782,6 +782,7 @@ void TextEdit::_notification(int p_what) {
|
||||||
int line = cursor.line_ofs - 1;
|
int line = cursor.line_ofs - 1;
|
||||||
// another row may be visible during smooth scrolling
|
// another row may be visible during smooth scrolling
|
||||||
int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0);
|
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++) {
|
for (int i = 0; i < draw_amount; i++) {
|
||||||
|
|
||||||
line++;
|
line++;
|
||||||
|
@ -1040,7 +1041,7 @@ void TextEdit::_notification(int p_what) {
|
||||||
|
|
||||||
if (brace_open_mismatch)
|
if (brace_open_mismatch)
|
||||||
color = cache.brace_mismatch_color;
|
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 (
|
if (
|
||||||
|
@ -1049,7 +1050,7 @@ void TextEdit::_notification(int p_what) {
|
||||||
|
|
||||||
if (brace_close_mismatch)
|
if (brace_close_mismatch)
|
||||||
color = cache.brace_mismatch_color;
|
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);
|
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;
|
char_ofs += im_char_width;
|
||||||
ofs++;
|
ofs++;
|
||||||
|
@ -1109,7 +1110,7 @@ void TextEdit::_notification(int p_what) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str[j] >= 32) {
|
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) {
|
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);
|
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);
|
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;
|
char_ofs += im_char_width;
|
||||||
ofs++;
|
ofs++;
|
||||||
|
|
|
@ -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", "Label", Color(1, 1, 1));
|
||||||
theme->set_color("font_color_shadow", "Label", Color(0, 0, 0, 0));
|
theme->set_color("font_color_shadow", "Label", Color(0, 0, 0, 0));
|
||||||
|
theme->set_color("font_outline_modulate", "Label", Color(1, 1, 1));
|
||||||
|
|
||||||
theme->set_constant("shadow_offset_x", "Label", 1 * scale);
|
theme->set_constant("shadow_offset_x", "Label", 1 * scale);
|
||||||
theme->set_constant("shadow_offset_y", "Label", 1 * scale);
|
theme->set_constant("shadow_offset_y", "Label", 1 * scale);
|
||||||
|
|
|
@ -33,8 +33,12 @@
|
||||||
#include "os/file_access.h"
|
#include "os/file_access.h"
|
||||||
#include "os/os.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;
|
return key < right.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,8 +216,8 @@ Error DynamicFontAtSize::_load() {
|
||||||
error = FT_Set_Pixel_Sizes(face, 0, id.size * oversampling);
|
error = FT_Set_Pixel_Sizes(face, 0, id.size * oversampling);
|
||||||
}
|
}
|
||||||
|
|
||||||
ascent = (face->size->metrics.ascender >> 6) / oversampling * scale_color_font;
|
ascent = (face->size->metrics.ascender / 64.0) / oversampling * scale_color_font;
|
||||||
descent = (-face->size->metrics.descender >> 6) / oversampling * scale_color_font;
|
descent = (-face->size->metrics.descender / 64.0) / oversampling * scale_color_font;
|
||||||
linegap = 0;
|
linegap = 0;
|
||||||
texture_flags = 0;
|
texture_flags = 0;
|
||||||
if (id.mipmaps)
|
if (id.mipmaps)
|
||||||
|
@ -241,18 +245,11 @@ float DynamicFontAtSize::get_descent() const {
|
||||||
return descent;
|
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)
|
if (!chr->found) {
|
||||||
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) {
|
|
||||||
|
|
||||||
//not found, try in fallbacks
|
//not found, try in fallbacks
|
||||||
for (int i = 0; i < p_fallbacks.size(); i++) {
|
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;
|
continue;
|
||||||
|
|
||||||
fb->_update_char(p_char);
|
fb->_update_char(p_char);
|
||||||
const Character *ch = fb->char_map.getptr(p_char);
|
const Character *fallback_chr = fb->char_map.getptr(p_char);
|
||||||
ERR_CONTINUE(!ch);
|
ERR_CONTINUE(!fallback_chr);
|
||||||
|
|
||||||
if (!ch->found)
|
if (!fallback_chr->found)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
c = ch;
|
return Pair<const Character *, DynamicFontAtSize *>(fallback_chr, fb);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//not found, try 0xFFFD to display 'not found'.
|
//not found, try 0xFFFD to display 'not found'.
|
||||||
|
const_cast<DynamicFontAtSize *>(this)->_update_char(0xFFFD);
|
||||||
if (!c->found) {
|
chr = char_map.getptr(0xFFFD);
|
||||||
|
ERR_FAIL_COND_V(!chr, (Pair<const Character *, DynamicFontAtSize *>(NULL, NULL)));
|
||||||
const_cast<DynamicFontAtSize *>(this)->_update_char(0xFFFD);
|
|
||||||
c = char_map.getptr(0xFFFD);
|
|
||||||
ERR_FAIL_COND_V(!c, Size2());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c->found) {
|
return Pair<const Character *, DynamicFontAtSize *>(chr, const_cast<DynamicFontAtSize *>(this));
|
||||||
ret.x = c->advance;
|
}
|
||||||
}
|
|
||||||
|
float DynamicFontAtSize::_get_kerning_advance(const DynamicFontAtSize *font, CharType p_char, CharType p_next) const {
|
||||||
|
float advance = 0.0;
|
||||||
|
|
||||||
if (p_next) {
|
if (p_next) {
|
||||||
FT_Vector delta;
|
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++) {
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// ensures oversampled glyphs will have enough space when this value is used by clipping/wrapping algorithms
|
||||||
ret.x = Math::ceil(ret.x);
|
ret.x = Math::ceil(ret.x);
|
||||||
return ret;
|
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)
|
if (!valid)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
const_cast<DynamicFontAtSize *>(this)->_update_char(p_char);
|
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
|
if (ch->found) {
|
||||||
bool used_fallback = false;
|
ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= font->textures.size(), 0);
|
||||||
|
|
||||||
for (int i = 0; i < p_fallbacks.size(); i++) {
|
if (!p_advance_only && ch->texture_idx != -1) {
|
||||||
|
|
||||||
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;
|
|
||||||
Point2 cpos = p_pos;
|
Point2 cpos = p_pos;
|
||||||
cpos.x += ch->h_align;
|
cpos.x += ch->h_align;
|
||||||
cpos.y -= fb->get_ascent();
|
cpos.y -= font->get_ascent();
|
||||||
cpos.y += ch->v_align;
|
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;
|
Color modulate = p_modulate;
|
||||||
if (FT_HAS_COLOR(face)) {
|
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) {
|
advance += _get_kerning_advance(font, p_char, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return advance;
|
return advance;
|
||||||
}
|
}
|
||||||
|
@ -443,96 +381,37 @@ void DynamicFontAtSize::_ft_stream_close(FT_Stream stream) {
|
||||||
memdelete(f);
|
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))
|
DynamicFontAtSize::TexturePosition DynamicFontAtSize::_find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height) {
|
||||||
return;
|
TexturePosition ret;
|
||||||
|
ret.index = -1;
|
||||||
|
ret.x = 0;
|
||||||
|
ret.y = 0;
|
||||||
|
|
||||||
_THREAD_SAFE_METHOD_
|
int mw = p_width;
|
||||||
|
int mh = p_height;
|
||||||
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;
|
|
||||||
|
|
||||||
for (int i = 0; i < textures.size(); i++) {
|
for (int i = 0; i < textures.size(); i++) {
|
||||||
|
|
||||||
CharTexture &ct = textures[i];
|
CharTexture &ct = textures[i];
|
||||||
|
|
||||||
if (ct.texture->get_format() != require_format)
|
if (ct.texture->get_format() != p_image_format)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (mw > ct.texture_size || mh > ct.texture_size) //too big for this texture
|
if (mw > ct.texture_size || mh > ct.texture_size) //too big for this texture
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
tex_y = 0x7FFFFFFF;
|
ret.y = 0x7FFFFFFF;
|
||||||
tex_x = 0;
|
ret.x = 0;
|
||||||
|
|
||||||
for (int j = 0; j < ct.texture_size - mw; j++) {
|
for (int j = 0; j < ct.texture_size - mw; j++) {
|
||||||
|
|
||||||
|
@ -545,25 +424,27 @@ void DynamicFontAtSize::_update_char(CharType p_char) {
|
||||||
max_y = y;
|
max_y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (max_y < tex_y) {
|
if (max_y < ret.y) {
|
||||||
tex_y = max_y;
|
ret.y = max_y;
|
||||||
tex_x = j;
|
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
|
continue; //fail, could not fit it here
|
||||||
|
|
||||||
tex_index = i;
|
ret.index = i;
|
||||||
break;
|
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));
|
||||||
//could not find texture to fit, create one
|
|
||||||
tex_x = 0;
|
|
||||||
tex_y = 0;
|
|
||||||
|
|
||||||
int texsize = MAX(id.size * 8, 256);
|
if (ret.index == -1) {
|
||||||
|
//could not find texture to fit, create one
|
||||||
|
ret.x = 0;
|
||||||
|
ret.y = 0;
|
||||||
|
|
||||||
|
int texsize = MAX(id.size * oversampling * 8, 256);
|
||||||
if (mw > texsize)
|
if (mw > texsize)
|
||||||
texsize = mw; //special case, adapt to it?
|
texsize = mw; //special case, adapt to it?
|
||||||
if (mh > texsize)
|
if (mh > texsize)
|
||||||
|
@ -575,13 +456,13 @@ void DynamicFontAtSize::_update_char(CharType p_char) {
|
||||||
|
|
||||||
CharTexture tex;
|
CharTexture tex;
|
||||||
tex.texture_size = texsize;
|
tex.texture_size = texsize;
|
||||||
tex.imgdata.resize(texsize * texsize * color_size);
|
tex.imgdata.resize(texsize * texsize * p_color_size); //grayscale alpha
|
||||||
|
|
||||||
{
|
{
|
||||||
//zero texture
|
//zero texture
|
||||||
PoolVector<uint8_t>::Write w = tex.imgdata.write();
|
PoolVector<uint8_t>::Write w = tex.imgdata.write();
|
||||||
ERR_FAIL_COND(texsize * texsize * color_size > tex.imgdata.size());
|
ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.imgdata.size(), ret);
|
||||||
for (int i = 0; i < texsize * texsize * color_size; i++) {
|
for (int i = 0; i < texsize * texsize * p_color_size; i++) {
|
||||||
w[i] = 0;
|
w[i] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -590,12 +471,31 @@ void DynamicFontAtSize::_update_char(CharType p_char) {
|
||||||
tex.offsets[i] = 0;
|
tex.offsets[i] = 0;
|
||||||
|
|
||||||
textures.push_back(tex);
|
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
|
//fit character in char texture
|
||||||
|
|
||||||
CharTexture &tex = textures[tex_index];
|
CharTexture &tex = textures[tex_pos.index];
|
||||||
|
|
||||||
{
|
{
|
||||||
PoolVector<uint8_t>::Write wr = tex.imgdata.write();
|
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 i = 0; i < h; i++) {
|
||||||
for (int j = 0; j < w; j++) {
|
for (int j = 0; j < w; j++) {
|
||||||
|
|
||||||
int ofs = ((i + tex_y + rect_margin) * tex.texture_size + j + tex_x + rect_margin) * color_size;
|
int ofs = ((i + tex_pos.y + rect_margin) * tex.texture_size + j + tex_pos.x + rect_margin) * color_size;
|
||||||
ERR_FAIL_COND(ofs >= tex.imgdata.size());
|
ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), Character::not_found());
|
||||||
switch (slot->bitmap.pixel_mode) {
|
switch (bitmap.pixel_mode) {
|
||||||
case FT_PIXEL_MODE_MONO: {
|
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));
|
int bit = 1 << (7 - (j % 8));
|
||||||
wr[ofs + 0] = 255; //grayscale as 1
|
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;
|
} break;
|
||||||
case FT_PIXEL_MODE_GRAY:
|
case FT_PIXEL_MODE_GRAY:
|
||||||
wr[ofs + 0] = 255; //grayscale as 1
|
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;
|
break;
|
||||||
case FT_PIXEL_MODE_BGRA: {
|
case FT_PIXEL_MODE_BGRA: {
|
||||||
int ofs_color = i * slot->bitmap.pitch + (j << 2);
|
int ofs_color = i * bitmap.pitch + (j << 2);
|
||||||
wr[ofs + 2] = slot->bitmap.buffer[ofs_color + 0];
|
wr[ofs + 2] = bitmap.buffer[ofs_color + 0];
|
||||||
wr[ofs + 1] = slot->bitmap.buffer[ofs_color + 1];
|
wr[ofs + 1] = bitmap.buffer[ofs_color + 1];
|
||||||
wr[ofs + 0] = slot->bitmap.buffer[ofs_color + 2];
|
wr[ofs + 0] = bitmap.buffer[ofs_color + 2];
|
||||||
wr[ofs + 3] = slot->bitmap.buffer[ofs_color + 3];
|
wr[ofs + 3] = bitmap.buffer[ofs_color + 3];
|
||||||
} break;
|
} break;
|
||||||
// TODO: FT_PIXEL_MODE_LCD
|
// TODO: FT_PIXEL_MODE_LCD
|
||||||
default:
|
default:
|
||||||
ERR_EXPLAIN("Font uses unsupported pixel format: " + itos(slot->bitmap.pixel_mode));
|
ERR_EXPLAIN("Font uses unsupported pixel format: " + itos(bitmap.pixel_mode));
|
||||||
ERR_FAIL();
|
ERR_FAIL_V(Character::not_found());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -648,31 +548,105 @@ void DynamicFontAtSize::_update_char(CharType p_char) {
|
||||||
|
|
||||||
// update height array
|
// update height array
|
||||||
|
|
||||||
for (int k = tex_x; k < tex_x + mw; k++) {
|
for (int k = tex_pos.x; k < tex_pos.x + mw; k++) {
|
||||||
|
tex.offsets[k] = tex_pos.y + mh;
|
||||||
tex.offsets[k] = tex_y + mh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Character chr;
|
Character chr;
|
||||||
chr.h_align = xofs * scale_color_font / oversampling;
|
chr.h_align = xofs * scale_color_font / oversampling;
|
||||||
chr.v_align = ascent - (yofs * scale_color_font / oversampling); // + ascent - descent;
|
chr.v_align = ascent - (yofs * scale_color_font / oversampling); // + ascent - descent;
|
||||||
chr.advance = advance * scale_color_font / oversampling;
|
chr.advance = advance * scale_color_font / oversampling;
|
||||||
chr.texture_idx = tex_index;
|
chr.texture_idx = tex_pos.index;
|
||||||
chr.found = true;
|
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 = chr.rect_uv;
|
||||||
chr.rect.position /= oversampling;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DynamicFontAtSize::update_oversampling() {
|
DynamicFontAtSize::Character DynamicFontAtSize::_make_outline_char(CharType p_char) {
|
||||||
if (oversampling == font_oversampling)
|
Character ret = Character::not_found();
|
||||||
return false;
|
|
||||||
if (!valid)
|
if (FT_Load_Char(face, p_char, FT_LOAD_NO_BITMAP | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)) != 0)
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicFontAtSize::update_oversampling() {
|
||||||
|
if (oversampling == font_oversampling || !valid)
|
||||||
|
return;
|
||||||
|
|
||||||
FT_Done_FreeType(library);
|
FT_Done_FreeType(library);
|
||||||
textures.clear();
|
textures.clear();
|
||||||
|
@ -680,8 +654,6 @@ bool DynamicFontAtSize::update_oversampling() {
|
||||||
oversampling = font_oversampling;
|
oversampling = font_oversampling;
|
||||||
valid = false;
|
valid = false;
|
||||||
_load();
|
_load();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicFontAtSize::DynamicFontAtSize() {
|
DynamicFontAtSize::DynamicFontAtSize() {
|
||||||
|
@ -710,11 +682,27 @@ DynamicFontAtSize::~DynamicFontAtSize() {
|
||||||
void DynamicFont::_reload_cache() {
|
void DynamicFont::_reload_cache() {
|
||||||
|
|
||||||
ERR_FAIL_COND(cache_id.size < 1);
|
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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
data_at_size = data->_get_dynamic_font_at_size(cache_id);
|
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++) {
|
for (int i = 0; i < fallbacks.size(); i++) {
|
||||||
fallback_data_at_size[i] = fallbacks[i]->_get_dynamic_font_at_size(cache_id);
|
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();
|
emit_changed();
|
||||||
|
@ -724,12 +712,10 @@ void DynamicFont::_reload_cache() {
|
||||||
void DynamicFont::set_font_data(const Ref<DynamicFontData> &p_data) {
|
void DynamicFont::set_font_data(const Ref<DynamicFontData> &p_data) {
|
||||||
|
|
||||||
data = p_data;
|
data = p_data;
|
||||||
if (data.is_valid())
|
_reload_cache();
|
||||||
data_at_size = data->_get_dynamic_font_at_size(cache_id);
|
|
||||||
else
|
|
||||||
data_at_size = Ref<DynamicFontAtSize>();
|
|
||||||
|
|
||||||
emit_changed();
|
emit_changed();
|
||||||
|
_change_notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref<DynamicFontData> DynamicFont::get_font_data() const {
|
Ref<DynamicFontData> DynamicFont::get_font_data() const {
|
||||||
|
@ -742,6 +728,7 @@ void DynamicFont::set_size(int p_size) {
|
||||||
if (cache_id.size == p_size)
|
if (cache_id.size == p_size)
|
||||||
return;
|
return;
|
||||||
cache_id.size = p_size;
|
cache_id.size = p_size;
|
||||||
|
outline_cache_id.size = p_size;
|
||||||
_reload_cache();
|
_reload_cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -750,6 +737,30 @@ int DynamicFont::get_size() const {
|
||||||
return cache_id.size;
|
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 {
|
bool DynamicFont::get_use_mipmaps() const {
|
||||||
|
|
||||||
return cache_id.mipmaps;
|
return cache_id.mipmaps;
|
||||||
|
@ -760,6 +771,7 @@ void DynamicFont::set_use_mipmaps(bool p_enable) {
|
||||||
if (cache_id.mipmaps == p_enable)
|
if (cache_id.mipmaps == p_enable)
|
||||||
return;
|
return;
|
||||||
cache_id.mipmaps = p_enable;
|
cache_id.mipmaps = p_enable;
|
||||||
|
outline_cache_id.mipmaps = p_enable;
|
||||||
_reload_cache();
|
_reload_cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -773,6 +785,7 @@ void DynamicFont::set_use_filter(bool p_enable) {
|
||||||
if (cache_id.filter == p_enable)
|
if (cache_id.filter == p_enable)
|
||||||
return;
|
return;
|
||||||
cache_id.filter = p_enable;
|
cache_id.filter = p_enable;
|
||||||
|
outline_cache_id.filter = p_enable;
|
||||||
_reload_cache();
|
_reload_cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -862,13 +875,24 @@ bool DynamicFont::is_distance_field_hint() const {
|
||||||
return false;
|
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 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) {
|
void DynamicFont::set_fallback(int p_idx, const Ref<DynamicFontData> &p_data) {
|
||||||
|
|
||||||
ERR_FAIL_COND(p_data.is_null());
|
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());
|
ERR_FAIL_COND(p_data.is_null());
|
||||||
fallbacks.push_back(p_data);
|
fallbacks.push_back(p_data);
|
||||||
fallback_data_at_size.push_back(fallbacks[fallbacks.size() - 1]->_get_dynamic_font_at_size(cache_id)); //const..
|
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();
|
_change_notify();
|
||||||
emit_changed();
|
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("set_size", "data"), &DynamicFont::set_size);
|
||||||
ClassDB::bind_method(D_METHOD("get_size"), &DynamicFont::get_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("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("get_use_mipmaps"), &DynamicFont::get_use_mipmaps);
|
||||||
ClassDB::bind_method(D_METHOD("set_use_filter", "enable"), &DynamicFont::set_use_filter);
|
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_GROUP("Settings", "");
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "size"), "set_size", "get_size");
|
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_mipmaps"), "set_use_mipmaps", "get_use_mipmaps");
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_filter"), "set_use_filter", "get_use_filter");
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_filter"), "set_use_filter", "get_use_filter");
|
||||||
ADD_GROUP("Extra Spacing", "extra_spacing");
|
ADD_GROUP("Extra Spacing", "extra_spacing");
|
||||||
|
@ -1008,6 +1042,7 @@ DynamicFont::DynamicFont() :
|
||||||
spacing_bottom = 0;
|
spacing_bottom = 0;
|
||||||
spacing_char = 0;
|
spacing_char = 0;
|
||||||
spacing_space = 0;
|
spacing_space = 0;
|
||||||
|
outline_color = Color(1, 1, 1);
|
||||||
if (dynamic_font_mutex)
|
if (dynamic_font_mutex)
|
||||||
dynamic_font_mutex->lock();
|
dynamic_font_mutex->lock();
|
||||||
dynamic_fonts.add(&font_list);
|
dynamic_fonts.add(&font_list);
|
||||||
|
@ -1043,7 +1078,8 @@ void DynamicFont::update_oversampling() {
|
||||||
SelfList<DynamicFont> *E = dynamic_fonts.first();
|
SelfList<DynamicFont> *E = dynamic_fonts.first();
|
||||||
while (E) {
|
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()));
|
changed.push_back(Ref<DynamicFont>(E->self()));
|
||||||
}
|
}
|
||||||
E = E->next();
|
E = E->next();
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include "io/resource_loader.h"
|
#include "io/resource_loader.h"
|
||||||
#include "os/mutex.h"
|
#include "os/mutex.h"
|
||||||
#include "os/thread_safe.h"
|
#include "os/thread_safe.h"
|
||||||
|
#include "pair.h"
|
||||||
#include "scene/resources/font.h"
|
#include "scene/resources/font.h"
|
||||||
|
|
||||||
#include <ft2build.h>
|
#include <ft2build.h>
|
||||||
|
@ -49,10 +50,10 @@ class DynamicFontData : public Resource {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct CacheID {
|
struct CacheID {
|
||||||
|
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
uint32_t size : 16;
|
uint32_t size : 16;
|
||||||
|
uint32_t outline_size : 8;
|
||||||
bool mipmaps : 1;
|
bool mipmaps : 1;
|
||||||
bool filter : 1;
|
bool filter : 1;
|
||||||
};
|
};
|
||||||
|
@ -148,8 +149,22 @@ class DynamicFontAtSize : public Reference {
|
||||||
texture_idx = 0;
|
texture_idx = 0;
|
||||||
v_align = 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 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);
|
static void _ft_stream_close(FT_Stream stream);
|
||||||
|
|
||||||
|
@ -174,10 +189,10 @@ public:
|
||||||
|
|
||||||
Size2 get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const;
|
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);
|
void set_texture_flags(uint32_t p_flags);
|
||||||
bool update_oversampling();
|
void update_oversampling();
|
||||||
|
|
||||||
DynamicFontAtSize();
|
DynamicFontAtSize();
|
||||||
~DynamicFontAtSize();
|
~DynamicFontAtSize();
|
||||||
|
@ -200,17 +215,23 @@ public:
|
||||||
private:
|
private:
|
||||||
Ref<DynamicFontData> data;
|
Ref<DynamicFontData> data;
|
||||||
Ref<DynamicFontAtSize> data_at_size;
|
Ref<DynamicFontAtSize> data_at_size;
|
||||||
|
Ref<DynamicFontAtSize> outline_data_at_size;
|
||||||
|
|
||||||
Vector<Ref<DynamicFontData> > fallbacks;
|
Vector<Ref<DynamicFontData> > fallbacks;
|
||||||
Vector<Ref<DynamicFontAtSize> > fallback_data_at_size;
|
Vector<Ref<DynamicFontAtSize> > fallback_data_at_size;
|
||||||
|
Vector<Ref<DynamicFontAtSize> > fallback_outline_data_at_size;
|
||||||
|
|
||||||
DynamicFontData::CacheID cache_id;
|
DynamicFontData::CacheID cache_id;
|
||||||
|
DynamicFontData::CacheID outline_cache_id;
|
||||||
|
|
||||||
bool valid;
|
bool valid;
|
||||||
int spacing_top;
|
int spacing_top;
|
||||||
int spacing_bottom;
|
int spacing_bottom;
|
||||||
int spacing_char;
|
int spacing_char;
|
||||||
int spacing_space;
|
int spacing_space;
|
||||||
|
|
||||||
|
Color outline_color;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void _reload_cache();
|
void _reload_cache();
|
||||||
|
|
||||||
|
@ -227,6 +248,12 @@ public:
|
||||||
void set_size(int p_size);
|
void set_size(int p_size);
|
||||||
int get_size() const;
|
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;
|
bool get_use_mipmaps() const;
|
||||||
void set_use_mipmaps(bool p_enable);
|
void set_use_mipmaps(bool p_enable);
|
||||||
|
|
||||||
|
@ -251,7 +278,9 @@ public:
|
||||||
|
|
||||||
virtual bool is_distance_field_hint() const;
|
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;
|
SelfList<DynamicFont> font_list;
|
||||||
|
|
||||||
|
|
|
@ -162,7 +162,7 @@ Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next) const {
|
||||||
return ret;
|
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);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Point2 cpos = p_pos;
|
if (!p_outline) {
|
||||||
cpos.x += c->h_align;
|
Point2 cpos = p_pos;
|
||||||
cpos.y -= get_ascent();
|
cpos.x += c->h_align;
|
||||||
cpos.y += c->v_align;
|
cpos.y -= get_ascent();
|
||||||
ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), 0);
|
cpos.y += c->v_align;
|
||||||
if (c->texture_idx != -1)
|
ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), 0);
|
||||||
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 (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());
|
//textures[c->texture_idx].texture->draw(p_canvas_item,Vector2());
|
||||||
|
|
||||||
|
@ -459,12 +461,12 @@ bool DynamicFont::is_distance_field_hint() const {
|
||||||
return false;
|
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())
|
if (!data_at_size.is_valid())
|
||||||
return 0;
|
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() {
|
DynamicFont::DynamicFont() {
|
||||||
|
|
|
@ -136,7 +136,7 @@ public:
|
||||||
|
|
||||||
Size2 get_char_size(CharType p_char, CharType p_next = 0) const;
|
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();
|
||||||
~DynamicFontAtSize();
|
~DynamicFontAtSize();
|
||||||
|
@ -171,7 +171,7 @@ public:
|
||||||
|
|
||||||
virtual bool is_distance_field_hint() const;
|
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();
|
||||||
~DynamicFont();
|
~DynamicFont();
|
||||||
|
|
|
@ -32,12 +32,12 @@
|
||||||
|
|
||||||
#include "core/io/resource_loader.h"
|
#include "core/io/resource_loader.h"
|
||||||
#include "core/os/file_access.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;
|
float length = get_string_size(p_text).width;
|
||||||
if (length >= p_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;
|
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");
|
ERR_PRINT("Unknown halignment type");
|
||||||
} break;
|
} 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;
|
Vector2 ofs;
|
||||||
|
|
||||||
|
int chars_drawn = 0;
|
||||||
|
bool with_outline = has_outline();
|
||||||
for (int i = 0; i < p_text.length(); i++) {
|
for (int i = 0; i < p_text.length(); i++) {
|
||||||
|
|
||||||
int width = get_char_size(p_text[i]).width;
|
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)
|
if (p_clip_w >= 0 && (ofs.x + width) > p_clip_w)
|
||||||
break; //clip
|
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() {
|
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_ascent"), &Font::get_ascent);
|
||||||
ClassDB::bind_method(D_METHOD("get_descent"), &Font::get_descent);
|
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("get_height"), &Font::get_height);
|
||||||
ClassDB::bind_method(D_METHOD("is_distance_field_hint"), &Font::is_distance_field_hint);
|
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("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);
|
ClassDB::bind_method(D_METHOD("update_changes"), &Font::update_changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,23 +504,24 @@ Ref<BitmapFont> BitmapFont::get_fallback() const {
|
||||||
return fallback;
|
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);
|
const Character *c = char_map.getptr(p_char);
|
||||||
|
|
||||||
if (!c) {
|
if (!c) {
|
||||||
if (fallback.is_valid())
|
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;
|
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);
|
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);
|
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;
|
return get_char_size(p_char, p_next).width;
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,14 +56,55 @@ public:
|
||||||
|
|
||||||
virtual bool is_distance_field_hint() const = 0;
|
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(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;
|
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 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;
|
|
||||||
|
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();
|
void update_changes();
|
||||||
Font();
|
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 {
|
class BitmapFont : public Font {
|
||||||
|
|
||||||
GDCLASS(BitmapFont, Font);
|
GDCLASS(BitmapFont, Font);
|
||||||
|
@ -153,7 +194,7 @@ public:
|
||||||
void set_distance_field_hint(bool p_distance_field);
|
void set_distance_field_hint(bool p_distance_field);
|
||||||
bool is_distance_field_hint() const;
|
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();
|
||||||
~BitmapFont();
|
~BitmapFont();
|
||||||
|
|
Loading…
Reference in a new issue