diff options
Diffstat (limited to 'scene')
-rw-r--r-- | scene/gui/item_list.cpp | 75 | ||||
-rw-r--r-- | scene/gui/item_list.h | 17 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 159 | ||||
-rw-r--r-- | scene/gui/text_edit.h | 22 | ||||
-rw-r--r-- | scene/register_scene_types.cpp | 5 | ||||
-rw-r--r-- | scene/resources/dynamic_font.cpp | 301 | ||||
-rw-r--r-- | scene/resources/dynamic_font.h | 76 | ||||
-rw-r--r-- | scene/resources/dynamic_font_stb.cpp | 527 | ||||
-rw-r--r-- | scene/resources/dynamic_font_stb.h | 178 |
9 files changed, 1148 insertions, 212 deletions
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index a616dc9254..fc4ab5f8ca 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -325,6 +325,18 @@ int ItemList::get_fixed_column_width() const{ return fixed_column_width; } +void ItemList::set_same_column_width(bool p_enable){ + + same_column_width=p_enable; + update(); + shape_changed=true; + +} +int ItemList::is_same_column_width() const{ + + return same_column_width; +} + void ItemList::set_max_text_lines(int p_lines){ ERR_FAIL_COND(p_lines<1); @@ -720,7 +732,7 @@ void ItemList::ensure_current_is_visible() { update(); } -static Size2 _adjust_to_max_size(Size2 p_size, Size2 p_max_size, bool p_stretch) { +static Size2 _adjust_to_max_size(Size2 p_size, Size2 p_max_size) { if (p_max_size.x<=0) p_max_size.x=1e20; @@ -730,10 +742,6 @@ static Size2 _adjust_to_max_size(Size2 p_size, Size2 p_max_size, bool p_stretch) Size2 new_size; - if (p_stretch && (p_size.x * p_size.y < p_max_size.x * p_max_size.y)) { - return p_max_size; - } - if (p_size.x > p_max_size.x) { new_size.width=p_max_size.x; @@ -828,6 +836,8 @@ void ItemList::_notification(int p_what) { } if (shape_changed) { + + float max_column_width = 0; //1- compute item minimum sizes for(int i=0;i<items.size();i++) { @@ -835,7 +845,7 @@ void ItemList::_notification(int p_what) { Size2 minsize; if (items[i].icon.is_valid()) { - minsize=_adjust_to_max_size(items[i].get_icon_size(),max_icon_size, icon_stretch); + minsize=_adjust_to_max_size(items[i].get_icon_size(),max_icon_size) * icon_scale; if (items[i].text!="") { if (icon_mode==ICON_MODE_TOP) { @@ -868,10 +878,11 @@ void ItemList::_notification(int p_what) { } - - items[i].rect_cache.size=minsize; if (fixed_column_width>0) - items[i].rect_cache.size.x=fixed_column_width; + minsize.x=fixed_column_width; + max_column_width=MAX(max_column_width,minsize.x); + items[i].rect_cache.size=minsize; + items[i].min_rect_cache.size=minsize; } @@ -900,17 +911,23 @@ void ItemList::_notification(int p_what) { break; } + items[i].rect_cache=items[i].min_rect_cache; + if(same_column_width) + items[i].rect_cache.size.x=max_column_width; items[i].rect_cache.pos=ofs; max_h=MAX(max_h,items[i].rect_cache.size.y); - ofs.x+=items[i].rect_cache.size.x; + ofs.x+=items[i].rect_cache.size.x + hseparation; //print_line("item "+itos(i)+" ofs "+rtos(items[i].rect_cache.size.x)); - if (col>0) - ofs.x+=hseparation; col++; if (col==current_columns) { if (i<items.size()-1) separators.push_back(ofs.y+max_h+vseparation/2); + + for(int j=i;j>=0 && col>0;j--, col--) { + items[j].rect_cache.size.y = max_h; + } + ofs.x=0; ofs.y+=max_h+vseparation; col=0; @@ -918,6 +935,10 @@ void ItemList::_notification(int p_what) { } } + for(int j=items.size()-1;j>=0 && col>0;j--, col--) { + items[j].rect_cache.size.y = max_h; + } + if (all_fit) { float max = MAX(page,ofs.y+max_h); scroll_bar->set_max(max); @@ -980,7 +1001,7 @@ void ItemList::_notification(int p_what) { Vector2 text_ofs; if (items[i].icon.is_valid()) { - Size2 icon_size = _adjust_to_max_size(items[i].get_icon_size(),max_icon_size, icon_stretch); + Size2 icon_size = _adjust_to_max_size(items[i].get_icon_size(),max_icon_size) * icon_scale; Vector2 icon_ofs; if (min_icon_size!=Vector2()) { @@ -992,7 +1013,12 @@ void ItemList::_notification(int p_what) { if (icon_mode==ICON_MODE_TOP) { pos.x += Math::floor((items[i].rect_cache.size.width - icon_size.width)/2); + pos.y += MIN( + Math::floor((items[i].rect_cache.size.height - icon_size.height)/2), + items[i].rect_cache.size.height - items[i].min_rect_cache.size.height + ); text_ofs.y = MAX(icon_size.height, min_icon_size.y) + icon_margin; + text_ofs.y += items[i].rect_cache.size.height - items[i].min_rect_cache.size.height; } else { pos.y += Math::floor((items[i].rect_cache.size.height - icon_size.height)/2); @@ -1018,6 +1044,8 @@ void ItemList::_notification(int p_what) { Vector2 size = font->get_string_size(items[i].text); if (fixed_column_width) max_len=fixed_column_width; + else if(same_column_width) + max_len=items[i].rect_cache.size.x; else max_len=size.x; @@ -1205,14 +1233,12 @@ bool ItemList::get_allow_rmb_select() const { return allow_rmb_select; } -void ItemList::set_icon_stretch_to_max_size(bool p_stretch) { - - icon_stretch = p_stretch; +void ItemList::set_icon_scale(real_t p_scale) { + icon_scale = p_scale; } -bool ItemList::get_icon_stretch_to_max_size() const { - - return icon_stretch; +real_t ItemList::get_icon_scale() const { + return icon_scale; } void ItemList::_bind_methods(){ @@ -1257,6 +1283,9 @@ void ItemList::_bind_methods(){ ObjectTypeDB::bind_method(_MD("set_fixed_column_width","width"),&ItemList::set_fixed_column_width); ObjectTypeDB::bind_method(_MD("get_fixed_column_width"),&ItemList::get_fixed_column_width); + ObjectTypeDB::bind_method(_MD("set_same_column_width","enable"),&ItemList::set_same_column_width); + ObjectTypeDB::bind_method(_MD("is_same_column_width"),&ItemList::is_same_column_width); + ObjectTypeDB::bind_method(_MD("set_max_text_lines","lines"),&ItemList::set_max_text_lines); ObjectTypeDB::bind_method(_MD("get_max_text_lines"),&ItemList::get_max_text_lines); @@ -1275,8 +1304,8 @@ void ItemList::_bind_methods(){ ObjectTypeDB::bind_method(_MD("set_max_icon_size","size"),&ItemList::set_max_icon_size); ObjectTypeDB::bind_method(_MD("get_max_icon_size"),&ItemList::get_max_icon_size); - ObjectTypeDB::bind_method(_MD("set_icon_stretch_to_max_size","stretch"),&ItemList::set_icon_stretch_to_max_size); - ObjectTypeDB::bind_method(_MD("get_icon_stretch_to_max_size"),&ItemList::get_icon_stretch_to_max_size); + ObjectTypeDB::bind_method(_MD("set_icon_scale","scale"),&ItemList::set_icon_scale); + ObjectTypeDB::bind_method(_MD("get_icon_scale"),&ItemList::get_icon_scale); ObjectTypeDB::bind_method(_MD("set_allow_rmb_select","allow"),&ItemList::set_allow_rmb_select); ObjectTypeDB::bind_method(_MD("get_allow_rmb_select"),&ItemList::get_allow_rmb_select); @@ -1309,6 +1338,7 @@ ItemList::ItemList() { icon_mode=ICON_MODE_LEFT; fixed_column_width=0; + same_column_width = false; max_text_lines=1; max_columns=1; @@ -1325,8 +1355,7 @@ ItemList::ItemList() { defer_select_single=-1; allow_rmb_select=false; - icon_stretch = false; - + icon_scale = 1.0f; } ItemList::~ItemList() { diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h index a79d2575bd..087c585128 100644 --- a/scene/gui/item_list.h +++ b/scene/gui/item_list.h @@ -33,6 +33,7 @@ private: Color custom_bg; Rect2 rect_cache; + Rect2 min_rect_cache; Size2 get_icon_size() const; @@ -44,8 +45,7 @@ private: bool shape_changed; bool ensure_selected_visible; - - bool icon_stretch; + bool same_column_width; Vector<Item> items; Vector<int> separators; @@ -61,12 +61,18 @@ private: int fixed_column_width; int max_text_lines; int max_columns; + Size2 min_icon_size; Size2 max_icon_size; + + Size2 max_item_size_cache; + int defer_select_single; bool allow_rmb_select; + real_t icon_scale; + void _scroll_changed(double); void _input_event(const InputEvent& p_event); protected: @@ -123,6 +129,9 @@ public: void set_fixed_column_width(int p_size); int get_fixed_column_width() const; + void set_same_column_width(bool p_enable); + int is_same_column_width() const; + void set_max_text_lines(int p_amount); int get_max_text_lines() const; @@ -152,8 +161,8 @@ public: virtual String get_tooltip(const Point2& p_pos) const; int get_item_at_pos(const Point2& p_pos,bool p_exact=false) const; - void set_icon_stretch_to_max_size(bool p_stretch); - bool get_icon_stretch_to_max_size() const; + void set_icon_scale(real_t p_scale); + real_t get_icon_scale() const; ItemList(); ~ItemList(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 03024daff5..ceb40925ec 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -314,6 +314,10 @@ void TextEdit::_update_scrollbars() { if (line_numbers) total_width += cache.line_number_w; + if (draw_breakpoint_gutter) { + total_width += cache.breakpoint_gutter_width; + } + bool use_hscroll=true; bool use_vscroll=true; @@ -412,9 +416,15 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { _update_caches(); - }; + } break; case NOTIFICATION_DRAW: { + if (draw_breakpoint_gutter) { + cache.breakpoint_gutter_width = breakpoint_gutter_width; + } else { + cache.breakpoint_gutter_width = 0; + } + int line_number_char_count=0; { @@ -439,7 +449,7 @@ void TextEdit::_notification(int p_what) { RID ci = get_canvas_item(); - int xmargin_beg=cache.style_normal->get_margin(MARGIN_LEFT)+cache.line_number_w; + int xmargin_beg=cache.style_normal->get_margin(MARGIN_LEFT)+cache.line_number_w+cache.breakpoint_gutter_width; int xmargin_end=cache.size.width-cache.style_normal->get_margin(MARGIN_RIGHT); //let's do it easy for now: cache.style_normal->draw(ci,Rect2(Point2(),cache.size)); @@ -682,9 +692,13 @@ void TextEdit::_notification(int p_what) { // check if line contains highlighted word int highlighted_text_col = -1; - if (highlighted_text.length() != 0) { - highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, 0); - } + int search_text_col = -1; + + if (!search_text.empty()) + search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); + + if (highlighted_text.length() != 0 && highlighted_text != search_text) + highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE|SEARCH_WHOLE_WORDS, 0); if (cache.line_number_w) { String fc = String::num(line+1); @@ -692,7 +706,7 @@ void TextEdit::_notification(int p_what) { fc="0"+fc; } - cache.font->draw(ci,Point2(cache.style_normal->get_margin(MARGIN_LEFT),ofs_y+cache.font->get_ascent()),fc,cache.line_number_color); + cache.font->draw(ci,Point2(cache.style_normal->get_margin(MARGIN_LEFT)+cache.breakpoint_gutter_width,ofs_y+cache.font->get_ascent()),fc,cache.line_number_color); } const Map<int,Text::ColorRegionInfo>& cri_map=text.get_color_region_info(line); @@ -706,6 +720,14 @@ void TextEdit::_notification(int p_what) { if (text.is_breakpoint(line)) { VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(xmargin_beg, ofs_y,xmargin_end-xmargin_beg,get_row_height()),cache.breakpoint_color); + + // draw breakpoint marker + if (draw_breakpoint_gutter) { + int vertical_gap = cache.breakpoint_gutter_width / 2; + int marker_size = cache.breakpoint_gutter_width - vertical_gap; + // no transparency on marker + VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(cache.style_normal->get_margin(MARGIN_LEFT) + 1, ofs_y + vertical_gap ,marker_size, marker_size),Color(cache.breakpoint_color.r, cache.breakpoint_color.g, cache.breakpoint_color.b)); + } } @@ -879,20 +901,45 @@ void TextEdit::_notification(int p_what) { break; } - bool in_selection = (selection.active && line>=selection.from_line && line<=selection.to_line && (line>selection.from_line || j>=selection.from_column) && (line<selection.to_line || j<selection.to_column)); + bool in_search_result=false; + if (search_text_col != -1) { + // if we are at the end check for new search result on same line + if (j >= search_text_col+search_text.length()) + search_text_col = _get_column_pos_of_word(search_text, str, search_flags, j); + + in_search_result = j >= search_text_col && j < search_text_col+search_text.length(); + + if (in_search_result) { + VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin, ofs_y ), Size2i(char_w, get_row_height())),cache.search_result_color); + } + } + + bool in_selection = (selection.active && line>=selection.from_line && line<=selection.to_line && (line>selection.from_line || j>=selection.from_column) && (line<selection.to_line || j<selection.to_column)); if (in_selection) { //inside selection! VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin, ofs_y ), Size2i(char_w,get_row_height())),cache.selection_color); } + if (in_search_result) { + Color border_color=(line==search_result_line && j>=search_result_col && j<search_result_col+search_text.length())?cache.font_color:cache.search_result_border_color; + + VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin, ofs_y ), Size2i(char_w,1)),border_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin, ofs_y+get_row_height()-1 ), Size2i(char_w,1)),border_color); + + if (j==search_text_col) + VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin, ofs_y ), Size2i(1,get_row_height())),border_color); + if (j==search_text_col+search_text.length()-1) + VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin+char_w-1, ofs_y ), Size2i(1,get_row_height())),border_color); + } + if (highlight_all_occurrences) { if (highlighted_text_col != -1) { // if we are at the end check for new word on same line if (j > highlighted_text_col+highlighted_text.length()) { - highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, j); + highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE|SEARCH_WHOLE_WORDS, j); } bool in_highlighted_word = (j >= highlighted_text_col && j < highlighted_text_col+highlighted_text.length()); @@ -1347,7 +1394,7 @@ void TextEdit::_get_mouse_pos(const Point2i& p_mouse, int &r_row, int &r_col) co col=text[row].size(); } else { - col=p_mouse.x-(cache.style_normal->get_margin(MARGIN_LEFT)+cache.line_number_w); + col=p_mouse.x-(cache.style_normal->get_margin(MARGIN_LEFT)+cache.line_number_w+cache.breakpoint_gutter_width); col+=cursor.x_ofs; col=get_char_pos_for( col, get_line(row) ); } @@ -1421,6 +1468,15 @@ void TextEdit::_input_event(const InputEvent& p_input_event) { int row,col; _get_mouse_pos(Point2i(mb.x,mb.y), row,col); + // toggle breakpoint on gutter click + if (draw_breakpoint_gutter) { + int gutter=cache.style_normal->get_margin(MARGIN_LEFT); + if (mb.x > gutter && mb.x <= gutter + cache.breakpoint_gutter_width + 3) { + set_line_as_breakpoint(row, !is_line_set_as_breakpoint(row)); + return; + } + } + int prev_col=cursor.column; int prev_line=cursor.line; @@ -1820,7 +1876,9 @@ void TextEdit::_input_event(const InputEvent& p_input_event) { } if (clear) { - begin_complex_operation(); + if (!dobreak) { + begin_complex_operation(); + } selection.active=false; update(); _remove_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column); @@ -1892,7 +1950,8 @@ void TextEdit::_input_event(const InputEvent& p_input_event) { if (completion_hint!="") { completion_hint=""; update(); - + } else { + scancode_handled=false; } } break; case KEY_TAB: { @@ -2846,7 +2905,7 @@ void TextEdit::adjust_viewport_to_cursor() { if (cursor.line_ofs>cursor.line) cursor.line_ofs=cursor.line; - int visible_width=cache.size.width-cache.style_normal->get_minimum_size().width-cache.line_number_w; + int visible_width=cache.size.width-cache.style_normal->get_minimum_size().width-cache.line_number_w-cache.breakpoint_gutter_width; if (v_scroll->is_visible()) visible_width-=v_scroll->get_combined_minimum_size().width; visible_width-=20; // give it a little more space @@ -3075,7 +3134,8 @@ void TextEdit::insert_text_at_cursor(const String& p_text) { } Control::CursorShape TextEdit::get_cursor_shape(const Point2& p_pos) const { - if(completion_active && completion_rect.has_point(p_pos)) { + int gutter=cache.style_normal->get_margin(MARGIN_LEFT)+cache.line_number_w+cache.breakpoint_gutter_width; + if((completion_active && completion_rect.has_point(p_pos)) || p_pos.x < gutter) { return CURSOR_ARROW; } return CURSOR_IBEAM; @@ -3220,6 +3280,8 @@ void TextEdit::_update_caches() { cache.breakpoint_color=get_color("breakpoint_color"); cache.brace_mismatch_color=get_color("brace_mismatch_color"); cache.word_highlighted_color=get_color("word_highlighted_color"); + cache.search_result_color=get_color("search_result_color"); + cache.search_result_border_color=get_color("search_result_border_color"); cache.line_spacing=get_constant("line_spacing"); cache.row_height = cache.font->get_height() + cache.line_spacing; cache.tab_icon=get_icon("tab"); @@ -3481,12 +3543,25 @@ String TextEdit::get_word_under_cursor() const { return text[cursor.line].substr(prev_cc, next_cc-prev_cc); } +void TextEdit::set_search_text(const String &p_search_text) { + search_text = p_search_text; +} + +void TextEdit::set_search_flags(uint32_t p_flags) { + search_flags = p_flags; +} + +void TextEdit::set_current_search_result(int line, int col) { + search_result_line = line; + search_result_col = col; +} + void TextEdit::set_highlight_all_occurrences(const bool p_enabled) { highlight_all_occurrences = p_enabled; update(); } -int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_search, int p_from_column) { +int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_search, uint32_t p_search_flags, int p_from_column) { int col = -1; if (p_key.length() > 0 && p_search.length() > 0) { @@ -3494,12 +3569,15 @@ int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_searc p_from_column = 0; } - while (col == -1 && p_from_column <= p_search.length()) { - // match case - col = p_search.findn(p_key, p_from_column); + while (col == -1 && p_from_column <= p_search.length()) { + if (p_search_flags&SEARCH_MATCH_CASE) { + col = p_search.find(p_key,p_from_column); + } else { + col = p_search.findn(p_key,p_from_column); + } // whole words only - if (col != -1) { + if (col != -1 && p_search_flags&SEARCH_WHOLE_WORDS) { p_from_column=col; if (col > 0 && _is_text_char(p_search[col-1])) { @@ -3565,10 +3643,8 @@ bool TextEdit::search(const String &p_key,uint32_t p_search_flags, int p_from_li //wrapped if (p_search_flags&SEARCH_BACKWARDS) { - text_line=text_line.substr(from_column,text_line.length()); from_column=text_line.length(); } else { - text_line=text_line.substr(0,from_column); from_column=0; } @@ -3579,7 +3655,6 @@ bool TextEdit::search(const String &p_key,uint32_t p_search_flags, int p_from_li } else { - //text_line=text_line.substr(0,p_from_column); //wrap around for missing begining. if (p_search_flags&SEARCH_BACKWARDS) from_column=text_line.length()-1; else @@ -3588,12 +3663,25 @@ bool TextEdit::search(const String &p_key,uint32_t p_search_flags, int p_from_li pos=-1; - if (!(p_search_flags&SEARCH_BACKWARDS)) { + int pos_from=0; + int last_pos=-1; + while ((last_pos=(p_search_flags&SEARCH_MATCH_CASE)?text_line.find(p_key,pos_from):text_line.findn(p_key,pos_from))!=-1) { - pos = (p_search_flags&SEARCH_MATCH_CASE)?text_line.find(p_key,from_column):text_line.findn(p_key,from_column); - } else { + if (p_search_flags&SEARCH_BACKWARDS) { + + if (last_pos>from_column) + break; + pos=last_pos; + + } else { - pos = (p_search_flags&SEARCH_MATCH_CASE)?text_line.rfind(p_key,from_column):text_line.rfindn(p_key,from_column); + if (last_pos>=from_column) { + pos=last_pos; + break; + } + } + + pos_from=last_pos+p_key.length(); } if (pos!=-1 && (p_search_flags&SEARCH_WHOLE_WORDS)) { @@ -4167,6 +4255,24 @@ void TextEdit::set_show_line_numbers(bool p_show) { update(); } +void TextEdit::set_draw_breakpoint_gutter(bool p_draw) { + draw_breakpoint_gutter = p_draw; + update(); +} + +bool TextEdit::is_drawing_breakpoint_gutter() const { + return draw_breakpoint_gutter; +} + +void TextEdit::set_breakpoint_gutter_width(int p_gutter_width) { + breakpoint_gutter_width = p_gutter_width; + update(); +} + +int TextEdit::get_breakpoint_gutter_width() const { + return cache.breakpoint_gutter_width; +} + bool TextEdit::is_text_field() const { return true; @@ -4308,6 +4414,8 @@ TextEdit::TextEdit() { cache.row_height=1; cache.line_spacing=1; cache.line_number_w=1; + cache.breakpoint_gutter_width=0; + breakpoint_gutter_width = 0; tab_size=4; text.set_tab_size(tab_size); @@ -4389,6 +4497,7 @@ TextEdit::TextEdit() { completion_line_ofs=0; tooltip_obj=NULL; line_numbers=false; + draw_breakpoint_gutter=false; next_operation_is_complex=false; scroll_past_end_of_file_enabled=false; auto_brace_completion_enabled=false; diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index dbe6293240..24a72afd48 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -88,10 +88,13 @@ class TextEdit : public Control { Color current_line_color; Color brace_mismatch_color; Color word_highlighted_color; + Color search_result_color; + Color search_result_border_color; int row_height; int line_spacing; int line_number_w; + int breakpoint_gutter_width; Size2 size; } cache; @@ -222,6 +225,8 @@ class TextEdit : public Control { bool text_changed_dirty; bool undo_enabled; bool line_numbers; + bool draw_breakpoint_gutter; + int breakpoint_gutter_width; bool highlight_all_occurrences; bool scroll_past_end_of_file_enabled; @@ -249,6 +254,11 @@ class TextEdit : public Control { bool callhint_below; Vector2 callhint_offset; + String search_text; + uint32_t search_flags; + int search_result_line; + int search_result_col; + int get_visible_rows() const; int get_char_count(); @@ -287,7 +297,7 @@ class TextEdit : public Control { String _base_get_text(int p_from_line, int p_from_column,int p_to_line,int p_to_column) const; void _base_remove_text(int p_from_line, int p_from_column,int p_to_line,int p_to_column); - int _get_column_pos_of_word(const String &p_key, const String &p_search, int p_from_column); + int _get_column_pos_of_word(const String &p_key, const String &p_search, uint32_t p_search_flags, int p_from_column); DVector<int> _search_bind(const String &p_key,uint32_t p_search_flags, int p_from_line,int p_from_column) const; @@ -408,6 +418,10 @@ public: void select(int p_from_line,int p_from_column,int p_to_line,int p_to_column); void deselect(); + void set_search_text(const String& p_search_text); + void set_search_flags(uint32_t p_flags); + void set_current_search_result(int line, int col); + void set_highlight_all_occurrences(const bool p_enabled); bool is_selection_active() const; int get_selection_from_line() const; @@ -451,6 +465,12 @@ public: void set_show_line_numbers(bool p_show); + void set_draw_breakpoint_gutter(bool p_draw); + bool is_drawing_breakpoint_gutter() const; + + void set_breakpoint_gutter_width(int p_gutter_width); + int get_breakpoint_gutter_width() const; + void set_tooltip_request_func(Object *p_obj, const StringName& p_function, const Variant& p_udata); void set_completion(bool p_enabled,const Vector<String>& p_prefixes); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 8327473a60..1fd1c77dca 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -133,6 +133,7 @@ #include "scene/resources/mesh_data_tool.h" #include "scene/resources/scene_preloader.h" #include "scene/resources/dynamic_font.h" +#include "scene/resources/dynamic_font_stb.h" #include "scene/main/timer.h" @@ -254,7 +255,6 @@ void register_scene_types() { resource_loader_wav = memnew( ResourceFormatLoaderWAV ); ResourceLoader::add_resource_format_loader( resource_loader_wav ); - resource_loader_dynamic_font = memnew( ResourceFormatLoaderDynamicFont ); ResourceLoader::add_resource_format_loader( resource_loader_dynamic_font ); @@ -578,8 +578,10 @@ void register_scene_types() { ObjectTypeDB::register_type<Animation>(); ObjectTypeDB::register_virtual_type<Font>(); ObjectTypeDB::register_type<BitmapFont>(); + ObjectTypeDB::register_type<DynamicFontData>(); ObjectTypeDB::register_type<DynamicFont>(); + ObjectTypeDB::register_type<StyleBoxEmpty>(); ObjectTypeDB::register_type<StyleBoxTexture>(); ObjectTypeDB::register_type<StyleBoxFlat>(); @@ -647,6 +649,7 @@ void unregister_scene_types() { memdelete( resource_loader_image ); memdelete( resource_loader_wav ); memdelete( resource_loader_dynamic_font ); + #ifdef TOOLS_ENABLED diff --git a/scene/resources/dynamic_font.cpp b/scene/resources/dynamic_font.cpp index 19aa0e79cc..c76b5f3d3a 100644 --- a/scene/resources/dynamic_font.cpp +++ b/scene/resources/dynamic_font.cpp @@ -1,65 +1,11 @@ +#ifdef FREETYPE_ENABLED #include "dynamic_font.h" -#define STB_TRUETYPE_IMPLEMENTATION -#include "stb_truetype.h" #include "os/file_access.h" -void DynamicFontData::lock() { - fr=font_data.read(); - - if (fr.ptr()!=last_data_ptr) { - - last_data_ptr=fr.ptr(); - - if (!stbtt_InitFont(&info, last_data_ptr, 0)) { - valid=false; - } else { - valid=true; - } - - last_data_ptr=fr.ptr(); - } -} - -void DynamicFontData::unlock() { - - fr = DVector<uint8_t>::Read(); -} - -void DynamicFontData::set_font_data(const DVector<uint8_t>& p_font) { - //clear caches and stuff - ERR_FAIL_COND(font_data.size()) ; - font_data=p_font; - - lock(); - - if (valid) { - stbtt_GetFontVMetrics(&info, &ascent, &descent, &linegap); - descent=-descent + linegap; - - for(int i=32;i<1024;i++) { - for(int j=32;j<1024;j++) { - - int kern = stbtt_GetCodepointKernAdvance(&info, i,j); - if (kern!=0) { - KerningPairKey kpk; - kpk.A=i; - kpk.B=j; - kerning_map[kpk]=kern; - } - } - } - } - - unlock(); - //clear existing stuff - - ERR_FAIL_COND(!valid); -} Ref<DynamicFontAtSize> DynamicFontData::_get_dynamic_font_at_size(int p_size) { - ERR_FAIL_COND_V(!valid,Ref<DynamicFontAtSize>()); if (size_cache.has(p_size)) { return Ref<DynamicFontAtSize>( size_cache[p_size] ); @@ -67,6 +13,7 @@ Ref<DynamicFontAtSize> DynamicFontData::_get_dynamic_font_at_size(int p_size) { Ref<DynamicFontAtSize> dfas; + dfas.instance(); dfas->font=Ref<DynamicFontData>( this ); @@ -74,21 +21,34 @@ Ref<DynamicFontAtSize> DynamicFontData::_get_dynamic_font_at_size(int p_size) { size_cache[p_size]=dfas.ptr(); dfas->size=p_size; + dfas->_load(); + + return dfas; - lock(); +} - dfas->scale = stbtt_ScaleForPixelHeight(&info, p_size); +void DynamicFontData::set_font_ptr(const uint8_t* p_font_mem,int p_font_mem_size) { - unlock(); + font_mem=p_font_mem; + font_mem_size=p_font_mem_size; +} - return dfas; +void DynamicFontData::set_font_path(const String& p_path) { + + font_path=p_path; +} + +void DynamicFontData::set_force_autohinter(bool p_force) { + force_autohinter=p_force; } DynamicFontData::DynamicFontData() { - last_data_ptr=NULL; - valid=false; + + force_autohinter=false; + font_mem=NULL; + font_mem_size=0; } DynamicFontData::~DynamicFontData() @@ -100,22 +60,106 @@ DynamicFontData::~DynamicFontData() //////////////////// +Error DynamicFontAtSize::_load() { + + + int error = FT_Init_FreeType( &library ); + + ERR_EXPLAIN(TTR("Error initializing FreeType.")); + ERR_FAIL_COND_V( error !=0, ERR_CANT_CREATE ); + + if (font->font_path!=String()) { + + FileAccess *f=FileAccess::open(font->font_path,FileAccess::READ); + ERR_FAIL_COND_V(!f,ERR_CANT_OPEN); + + memset(&stream,0,sizeof(FT_StreamRec)); + stream.base=NULL; + stream.size=f->get_len(); + stream.pos=0; + stream.descriptor.pointer=f; + stream.read=_ft_stream_io; + stream.close=_ft_stream_close; + + FT_Open_Args fargs; + memset(&fargs,0,sizeof(FT_Open_Args)); + fargs.flags=FT_OPEN_STREAM; + fargs.stream=&stream; + error = FT_Open_Face( library,&fargs,0,&face); + } else if (font->font_mem) { + + memset(&stream,0,sizeof(FT_StreamRec)); + stream.base=(unsigned char*)font->font_mem; + stream.size=font->font_mem_size; + stream.pos=0; + + FT_Open_Args fargs; + memset(&fargs,0,sizeof(FT_Open_Args)); + fargs.memory_base=(unsigned char*)font->font_mem; + fargs.memory_size=font->font_mem_size; + fargs.flags= FT_OPEN_MEMORY; + fargs.stream=&stream; + error = FT_Open_Face( library,&fargs,0,&face); + + } else { + ERR_EXPLAIN("DynamicFont uninitialized"); + ERR_FAIL_V(ERR_UNCONFIGURED); + } + + //error = FT_New_Face( library, src_path.utf8().get_data(),0,&face ); + + if ( error == FT_Err_Unknown_File_Format ) { + ERR_EXPLAIN(TTR("Unknown font format.")); + FT_Done_FreeType( library ); + + } else if ( error ) { + + ERR_EXPLAIN(TTR("Error loading font.")); + FT_Done_FreeType( library ); + + } + + ERR_FAIL_COND_V(error,ERR_FILE_CANT_OPEN); + + + /*error = FT_Set_Char_Size(face,0,64*size,512,512); + + if ( error ) { + FT_Done_FreeType( library ); + ERR_EXPLAIN(TTR("Invalid font size.")); + ERR_FAIL_COND_V( error, ERR_INVALID_PARAMETER ); + }*/ + + error = FT_Set_Pixel_Sizes(face,0,size); + + ascent=face->size->metrics.ascender>>6; + descent=-face->size->metrics.descender>>6; + linegap=0; + + //print_line("ASCENT: "+itos(ascent)+" descent "+itos(descent)+" hinted: "+itos(face->face_flags&FT_FACE_FLAG_HINTER)); + + valid=true; + return OK; +} + float DynamicFontAtSize::get_height() const { - return (font->ascent+font->descent)*scale; + return ascent+descent; } float DynamicFontAtSize::get_ascent() const { - return font->ascent*scale; + return ascent; } float DynamicFontAtSize::get_descent() const { - return font->descent*scale; + return descent; } Size2 DynamicFontAtSize::get_char_size(CharType p_char,CharType p_next) const { + if (!valid) + return Size2(1,1); const_cast<DynamicFontAtSize*>(this)->_update_char(p_char); const Character *c = char_map.getptr(p_char); @@ -124,14 +168,9 @@ Size2 DynamicFontAtSize::get_char_size(CharType p_char,CharType p_next) const { Size2 ret( c->advance, get_height()); if (p_next) { - DynamicFontData::KerningPairKey kpk; - kpk.A=p_char; - kpk.B=p_next; - - const Map<DynamicFontData::KerningPairKey,int>::Element *K=font->kerning_map.find(kpk); - if (K) { - ret.x+=K->get()*scale; - } + FT_Vector delta; + FT_Get_Kerning( face, p_char,p_next, FT_KERNING_DEFAULT, &delta ); + ret.x+=delta.x>>6; } @@ -141,6 +180,9 @@ Size2 DynamicFontAtSize::get_char_size(CharType p_char,CharType p_next) const { float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2& p_pos, const CharType& p_char,const CharType& p_next,const Color& p_modulate) const { + if (!valid) + return 0; + const_cast<DynamicFontAtSize*>(this)->_update_char(p_char); const Character * c = char_map.getptr(p_char); @@ -161,58 +203,84 @@ float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2& p_pos, const float ret = c->advance; if (p_next) { - DynamicFontData::KerningPairKey kpk; - kpk.A=p_char; - kpk.B=p_next; - const Map<DynamicFontData::KerningPairKey,int>::Element *K=font->kerning_map.find(kpk); - if (K) { - ret+=K->get()*scale; - } + FT_Vector delta; + FT_Get_Kerning( face, p_char,p_next, FT_KERNING_DEFAULT, &delta ); + ret+=delta.x>>6; } return ret; } +unsigned long DynamicFontAtSize::_ft_stream_io(FT_Stream stream, unsigned long offset, unsigned char* buffer, unsigned long count ) { + + + FileAccess *f=(FileAccess*)stream->descriptor.pointer; + + if (f->get_pos()!=offset) { + f->seek(offset); + + } + + if (count==0) + return 0; + + return f->get_buffer(buffer,count); +} +void DynamicFontAtSize::_ft_stream_close(FT_Stream stream) { + + FileAccess *f=(FileAccess*)stream->descriptor.pointer; + f->close(); + memdelete(f); +} + void DynamicFontAtSize::_update_char(CharType p_char) { if (char_map.has(p_char)) return; - font->lock(); - + _THREAD_SAFE_METHOD_ - int w,h,xofs,yofs; - unsigned char * cpbitmap = stbtt_GetCodepointBitmap(&font->info, scale, scale, p_char, &w, &h, &xofs, &yofs ); + FT_GlyphSlot slot = face->glyph; - if (!cpbitmap) { - //no glyph + int error = FT_Load_Char( face, p_char, FT_LOAD_RENDER|(font->force_autohinter?FT_LOAD_FORCE_AUTOHINT:0) ); + if (!error) { + error = FT_Render_Glyph( face->glyph, ft_render_mode_normal ); + } + if (error) { - int advance; - stbtt_GetCodepointHMetrics(&font->info, p_char, &advance, 0); + int advance=0; + //stbtt_GetCodepointHMetrics(&font->info, p_char, &advance, 0); //print_line("char has no bitmap: "+itos(p_char)+" but advance is "+itos(advance*scale)); Character ch; ch.texture_idx=-1; - ch.advance=advance*scale; + ch.advance=advance; ch.h_align=0; ch.v_align=0; char_map[p_char]=ch; - font->unlock(); return; } + + + int w = slot->bitmap.width; + int h = slot->bitmap.rows; + int p = slot->bitmap.pitch; + int yofs=slot->bitmap_top; + int xofs=slot->bitmap_left; + int advance=slot->advance.x>>6; + + int mw=w+rect_margin*2; int mh=h+rect_margin*2; if (mw>4096 || mh>4096) { - stbtt_FreeBitmap(cpbitmap,NULL); - font->unlock(); ERR_FAIL_COND(mw>4096); ERR_FAIL_COND(mh>4096); } @@ -304,13 +372,14 @@ void DynamicFontAtSize::_update_char(CharType p_char) { { DVector<uint8_t>::Write wr = tex.imgdata.write(); + for(int i=0;i<h;i++) { for(int j=0;j<w;j++) { int ofs = ( (i+tex_y+rect_margin)*tex.texture_size+j+tex_x+rect_margin)*2; ERR_FAIL_COND(ofs >= tex.imgdata.size()); wr[ofs+0]=255; //grayscale as 1 - wr[ofs+1]=cpbitmap[i*w+j]; //alpha as 0 + wr[ofs+1]=slot->bitmap.buffer[i*slot->bitmap.width+j]; } } } @@ -322,7 +391,7 @@ void DynamicFontAtSize::_update_char(CharType p_char) { if (tex.texture.is_null()) { tex.texture.instance(); - tex.texture->create_from_image(img,Texture::FLAG_FILTER); + tex.texture->create_from_image(img,0/*Texture::FLAG_FILTER*/); } else { tex.texture->set_data(img); //update } @@ -337,13 +406,11 @@ void DynamicFontAtSize::_update_char(CharType p_char) { tex.offsets[k]=tex_y+mh; } - int advance; - stbtt_GetCodepointHMetrics(&font->info, p_char, &advance, 0); Character chr; chr.h_align=xofs; - chr.v_align=yofs + get_ascent(); - chr.advance=advance*scale; + chr.v_align=ascent-yofs;// + ascent - descent; + chr.advance=advance; chr.texture_idx=tex_index; @@ -353,21 +420,24 @@ void DynamicFontAtSize::_update_char(CharType p_char) { char_map[p_char]=chr; - stbtt_FreeBitmap(cpbitmap,NULL); - - font->unlock(); } DynamicFontAtSize::DynamicFontAtSize() { + valid=false; rect_margin=1; + ascent=1; + descent=1; + linegap=1; } DynamicFontAtSize::~DynamicFontAtSize(){ - ERR_FAIL_COND(!font.ptr()); - font->size_cache.erase(size); + if (valid) { + FT_Done_FreeType( library ); + font->size_cache.erase(size); + } } ///////////////////////// @@ -478,34 +548,21 @@ RES ResourceFormatLoaderDynamicFont::load(const String &p_path, const String& p_ if (r_error) *r_error=ERR_FILE_CANT_OPEN; + Ref<DynamicFontData> dfont; + dfont.instance();; + dfont->set_font_path(p_path); - FileAccess *f = FileAccess::open(p_path,FileAccess::READ); - ERR_FAIL_COND_V(!f,RES()); - - DVector<uint8_t> data; - - data.resize(f->get_len()); - - ERR_FAIL_COND_V(data.size()==0,RES()); - - { - DVector<uint8_t>::Write w = data.write(); - f->get_buffer(w.ptr(),data.size()); - } - - Ref<DynamicFontData> dfd; - dfd.instance(); - dfd->set_font_data(data); if (r_error) *r_error=OK; - return dfd; + return dfont; } void ResourceFormatLoaderDynamicFont::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("ttf"); + p_extensions->push_back("otf"); } bool ResourceFormatLoaderDynamicFont::handles_type(const String& p_type) const { @@ -516,8 +573,10 @@ bool ResourceFormatLoaderDynamicFont::handles_type(const String& p_type) const { String ResourceFormatLoaderDynamicFont::get_resource_type(const String &p_path) const { String el = p_path.extension().to_lower(); - if (el=="ttf") + if (el=="ttf" || el=="otf") return "DynamicFontData"; return ""; } + +#endif diff --git a/scene/resources/dynamic_font.h b/scene/resources/dynamic_font.h index ba7249a7b7..f0cf80b042 100644 --- a/scene/resources/dynamic_font.h +++ b/scene/resources/dynamic_font.h @@ -1,10 +1,14 @@ -#ifndef DYNAMICFONT_H -#define DYNAMICFONT_H +#ifndef DYNAMIC_FONT_H +#define DYNAMIC_FONT_H -#include "font.h" -#include "stb_truetype.h" +#ifdef FREETYPE_ENABLED +#include "scene/resources/font.h" +#include "os/thread_safe.h" #include "io/resource_loader.h" +#include <ft2build.h> +#include FT_FREETYPE_H + class DynamicFontAtSize; class DynamicFont; @@ -13,39 +17,16 @@ class DynamicFontData : public Resource { OBJ_TYPE(DynamicFontData,Resource); - bool valid; - - DVector<uint8_t> font_data; - DVector<uint8_t>::Read fr; - const uint8_t* last_data_ptr; - - struct KerningPairKey { - - union { - struct { - uint32_t A,B; - }; - - uint64_t pair; - }; - - _FORCE_INLINE_ bool operator<(const KerningPairKey& p_r) const { return pair<p_r.pair; } - }; - Map<KerningPairKey,int> kerning_map; + const uint8_t *font_mem; + int font_mem_size; + bool force_autohinter; + String font_path; Map<int,DynamicFontAtSize*> size_cache; -friend class DynamicFontAtSize; - - stbtt_fontinfo info; - int ascent; - int descent; - int linegap; - - void lock(); - void unlock(); + friend class DynamicFontAtSize; friend class DynamicFont; @@ -53,7 +34,10 @@ friend class DynamicFont; Ref<DynamicFontAtSize> _get_dynamic_font_at_size(int p_size); public: - void set_font_data(const DVector<uint8_t>& p_font); + void set_font_ptr(const uint8_t* p_font_mem,int p_font_mem_size); + void set_font_path(const String& p_path); + void set_force_autohinter(bool p_force); + DynamicFontData(); ~DynamicFontData(); }; @@ -61,11 +45,21 @@ public: class DynamicFontAtSize : public Reference { - OBJ_TYPE(DynamicFontAtSize,Reference); + OBJ_TYPE(DynamicFontAtSize,Reference) + _THREAD_SAFE_CLASS_ + FT_Library library; /* handle to library */ + FT_Face face; /* handle to face object */ + FT_StreamRec stream; + + int ascent; + int descent; + int linegap; int rect_margin; + bool valid; + struct CharTexture { DVector<uint8_t> imgdata; @@ -88,6 +82,8 @@ class DynamicFontAtSize : public Reference { }; + 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); HashMap< CharType, Character > char_map; @@ -95,13 +91,18 @@ class DynamicFontAtSize : public Reference { friend class DynamicFontData; Ref<DynamicFontData> font; - float scale; int size; + + + Error _load(); protected: + + public: + float get_height() const; float get_ascent() const; @@ -126,7 +127,7 @@ class DynamicFont : public Font { Ref<DynamicFontData> data; Ref<DynamicFontAtSize> data_at_size; int size; - + bool valid; protected: @@ -171,5 +172,6 @@ public: }; +#endif -#endif // DYNAMICFONT_H +#endif diff --git a/scene/resources/dynamic_font_stb.cpp b/scene/resources/dynamic_font_stb.cpp new file mode 100644 index 0000000000..344043fcdd --- /dev/null +++ b/scene/resources/dynamic_font_stb.cpp @@ -0,0 +1,527 @@ +#include "dynamic_font_stb.h" + +#ifndef FREETYPE_ENABLED + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" +#include "os/file_access.h" + +void DynamicFontData::lock() { + + fr=font_data.read(); + + if (fr.ptr()!=last_data_ptr) { + + last_data_ptr=fr.ptr(); + + if (!stbtt_InitFont(&info, last_data_ptr, 0)) { + valid=false; + } else { + valid=true; + } + + last_data_ptr=fr.ptr(); + } +} + +void DynamicFontData::unlock() { + + fr = DVector<uint8_t>::Read(); +} + +void DynamicFontData::set_font_data(const DVector<uint8_t>& p_font) { + //clear caches and stuff + ERR_FAIL_COND(font_data.size()) ; + font_data=p_font; + + lock(); + + if (valid) { + stbtt_GetFontVMetrics(&info, &ascent, &descent, &linegap); + descent=-descent + linegap; + + for(int i=32;i<1024;i++) { + for(int j=32;j<1024;j++) { + + int kern = stbtt_GetCodepointKernAdvance(&info, i,j); + if (kern!=0) { + KerningPairKey kpk; + kpk.A=i; + kpk.B=j; + kerning_map[kpk]=kern; + } + } + } + } + + unlock(); + //clear existing stuff + + ERR_FAIL_COND(!valid); +} + +Ref<DynamicFontAtSize> DynamicFontData::_get_dynamic_font_at_size(int p_size) { + + ERR_FAIL_COND_V(!valid,Ref<DynamicFontAtSize>()); + + if (size_cache.has(p_size)) { + return Ref<DynamicFontAtSize>( size_cache[p_size] ); + } + + + Ref<DynamicFontAtSize> dfas; + dfas.instance(); + + dfas->font=Ref<DynamicFontData>( this ); + + size_cache[p_size]=dfas.ptr(); + + dfas->size=p_size; + + lock(); + + dfas->scale = stbtt_ScaleForPixelHeight(&info, p_size); + + unlock(); + + return dfas; + +} + +DynamicFontData::DynamicFontData() +{ + last_data_ptr=NULL; + valid=false; +} + +DynamicFontData::~DynamicFontData() +{ + +} + + + +//////////////////// + +float DynamicFontAtSize::get_height() const { + + return (font->ascent+font->descent)*scale; +} + +float DynamicFontAtSize::get_ascent() const { + + return font->ascent*scale; +} +float DynamicFontAtSize::get_descent() const { + + return font->descent*scale; +} + +Size2 DynamicFontAtSize::get_char_size(CharType p_char,CharType p_next) const { + + const_cast<DynamicFontAtSize*>(this)->_update_char(p_char); + + const Character *c = char_map.getptr(p_char); + ERR_FAIL_COND_V(!c,Size2()); + + Size2 ret( c->advance, get_height()); + + if (p_next) { + DynamicFontData::KerningPairKey kpk; + kpk.A=p_char; + kpk.B=p_next; + + const Map<DynamicFontData::KerningPairKey,int>::Element *K=font->kerning_map.find(kpk); + if (K) { + ret.x+=K->get()*scale; + } + + } + + return ret; +} + + +float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2& p_pos, const CharType& p_char,const CharType& p_next,const Color& p_modulate) const { + + const_cast<DynamicFontAtSize*>(this)->_update_char(p_char); + + const Character * c = char_map.getptr(p_char); + + if (!c) { + 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 ); + + //textures[c->texture_idx].texture->draw(p_canvas_item,Vector2()); + + float ret = c->advance; + if (p_next) { + DynamicFontData::KerningPairKey kpk; + kpk.A=p_char; + kpk.B=p_next; + + const Map<DynamicFontData::KerningPairKey,int>::Element *K=font->kerning_map.find(kpk); + if (K) { + ret+=K->get()*scale; + } + + } + + return ret; +} + + +void DynamicFontAtSize::_update_char(CharType p_char) { + + if (char_map.has(p_char)) + return; + + font->lock(); + + + int w,h,xofs,yofs; + unsigned char * cpbitmap = stbtt_GetCodepointBitmap(&font->info, scale, scale, p_char, &w, &h, &xofs, &yofs ); + + if (!cpbitmap) { + //no glyph + + int advance; + stbtt_GetCodepointHMetrics(&font->info, p_char, &advance, 0); + //print_line("char has no bitmap: "+itos(p_char)+" but advance is "+itos(advance*scale)); + Character ch; + ch.texture_idx=-1; + ch.advance=advance*scale; + ch.h_align=0; + ch.v_align=0; + + char_map[p_char]=ch; + + font->unlock(); + + return; + } + + int mw=w+rect_margin*2; + int mh=h+rect_margin*2; + + if (mw>4096 || mh>4096) { + + stbtt_FreeBitmap(cpbitmap,NULL); + font->unlock(); + ERR_FAIL_COND(mw>4096); + ERR_FAIL_COND(mh>4096); + } + + //find a texture to fit this... + + int tex_index=-1; + int tex_x=0; + int tex_y=0; + + for(int i=0;i<textures.size();i++) { + + CharTexture &ct=textures[i]; + + if (mw > ct.texture_size || mh > ct.texture_size) //too big for this texture + continue; + + tex_y=0x7FFFFFFF; + tex_x=0; + + for(int j=0;j<ct.texture_size-mw;j++) { + + int max_y=0; + + for(int k=j;k<j+mw;k++) { + + int y = ct.offsets[k]; + if (y>max_y) + max_y=y; + } + + if (max_y<tex_y) { + tex_y=max_y; + tex_x=j; + } + } + + if (tex_y==0x7FFFFFFF || tex_y+mh > ct.texture_size) + continue; //fail, could not fit it here + + tex_index=i; + break; + } + +// print_line("CHAR: "+String::chr(p_char)+" TEX INDEX: "+itos(tex_index)+" X: "+itos(tex_x)+" Y: "+itos(tex_y)); + + if (tex_index==-1) { + //could not find texture to fit, create one + tex_x = 0; + tex_y = 0; + + int texsize = MAX(size*8,256); + if (mw>texsize) + texsize=mw; //special case, adapt to it? + if (mh>texsize) + texsize=mh; //special case, adapt to it? + + texsize=nearest_power_of_2(texsize); + + texsize=MIN(texsize,4096); + + + CharTexture tex; + tex.texture_size=texsize; + tex.imgdata.resize(texsize*texsize*2); //grayscale alpha + + { + //zero texture + DVector<uint8_t>::Write w = tex.imgdata.write(); + ERR_FAIL_COND(texsize*texsize*2 > tex.imgdata.size()); + for(int i=0;i<texsize*texsize*2;i++) { + w[i]=0; + } + } + tex.offsets.resize(texsize); + for(int i=0;i<texsize;i++) //zero offsets + tex.offsets[i]=0; + + textures.push_back(tex); + tex_index=textures.size()-1; + + } + + + //fit character in char texture + + CharTexture &tex=textures[tex_index]; + + { + DVector<uint8_t>::Write wr = tex.imgdata.write(); + + for(int i=0;i<h;i++) { + for(int j=0;j<w;j++) { + + int ofs = ( (i+tex_y+rect_margin)*tex.texture_size+j+tex_x+rect_margin)*2; + ERR_FAIL_COND(ofs >= tex.imgdata.size()); + wr[ofs+0]=255; //grayscale as 1 + wr[ofs+1]=cpbitmap[i*w+j]; //alpha as 0 + } + } + } + + //blit to image and texture + { + + Image img(tex.texture_size,tex.texture_size,0,Image::FORMAT_GRAYSCALE_ALPHA,tex.imgdata); + + if (tex.texture.is_null()) { + tex.texture.instance(); + tex.texture->create_from_image(img,Texture::FLAG_FILTER); + } else { + tex.texture->set_data(img); //update + } + + } + + + // update height array + + for(int k=tex_x;k<tex_x+mw;k++) { + + tex.offsets[k]=tex_y+mh; + } + + int advance; + stbtt_GetCodepointHMetrics(&font->info, p_char, &advance, 0); + + Character chr; + chr.h_align=xofs; + chr.v_align=yofs + get_ascent(); + chr.advance=advance*scale; + chr.texture_idx=tex_index; + + + chr.rect=Rect2(tex_x+rect_margin,tex_y+rect_margin,w,h); + + //print_line("CHAR: "+String::chr(p_char)+" TEX INDEX: "+itos(tex_index)+" RECT: "+chr.rect+" X OFS: "+itos(xofs)+" Y OFS: "+itos(yofs)); + + char_map[p_char]=chr; + + stbtt_FreeBitmap(cpbitmap,NULL); + + font->unlock(); + +} + +DynamicFontAtSize::DynamicFontAtSize() { + + rect_margin=1; +} + +DynamicFontAtSize::~DynamicFontAtSize(){ + + ERR_FAIL_COND(!font.ptr()); + font->size_cache.erase(size); +} + +///////////////////////// + + +void DynamicFont::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("set_font_data","data:DynamicFontData"),&DynamicFont::set_font_data); + ObjectTypeDB::bind_method(_MD("get_font_data:DynamicFontData"),&DynamicFont::get_font_data); + + ObjectTypeDB::bind_method(_MD("set_size","data"),&DynamicFont::set_size); + ObjectTypeDB::bind_method(_MD("get_size"),&DynamicFont::get_size); + + ADD_PROPERTY(PropertyInfo(Variant::INT,"size"),_SCS("set_size"),_SCS("get_size")); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT,"font",PROPERTY_HINT_RESOURCE_TYPE,"DynamicFontData"),_SCS("set_font_data"),_SCS("get_font_data")); +} + + +void DynamicFont::set_font_data(const Ref<DynamicFontData>& p_data) { + + data=p_data; + data_at_size=data->_get_dynamic_font_at_size(size); +} + +Ref<DynamicFontData> DynamicFont::get_font_data() const{ + + return data; +} + +void DynamicFont::set_size(int p_size){ + + if (size==p_size) + return; + size=p_size; + ERR_FAIL_COND(p_size<1); + if (!data.is_valid()) + return; + data_at_size=data->_get_dynamic_font_at_size(size); + +} +int DynamicFont::get_size() const{ + + return size; +} + +float DynamicFont::get_height() const{ + + if (!data_at_size.is_valid()) + return 1; + + return data_at_size->get_height(); +} + +float DynamicFont::get_ascent() const{ + + if (!data_at_size.is_valid()) + return 1; + + return data_at_size->get_ascent(); +} + +float DynamicFont::get_descent() const{ + + if (!data_at_size.is_valid()) + return 1; + + return data_at_size->get_descent(); + +} + +Size2 DynamicFont::get_char_size(CharType p_char,CharType p_next) const{ + + if (!data_at_size.is_valid()) + return Size2(1,1); + + return data_at_size->get_char_size(p_char,p_next); + +} + +bool DynamicFont::is_distance_field_hint() const{ + + return false; +} + +float DynamicFont::draw_char(RID p_canvas_item, const Point2& p_pos, const CharType& p_char,const CharType& p_next,const Color& p_modulate) const { + + if (!data_at_size.is_valid()) + return 0; + + return data_at_size->draw_char(p_canvas_item,p_pos,p_char,p_next,p_modulate); + +} + +DynamicFont::DynamicFont() { + + size=16; +} + +DynamicFont::~DynamicFont() { + +} + +///////////////////////// + + +RES ResourceFormatLoaderDynamicFont::load(const String &p_path, const String& p_original_path, Error *r_error) { + + if (r_error) + *r_error=ERR_FILE_CANT_OPEN; + + + FileAccess *f = FileAccess::open(p_path,FileAccess::READ); + ERR_FAIL_COND_V(!f,RES()); + + DVector<uint8_t> data; + + data.resize(f->get_len()); + + ERR_FAIL_COND_V(data.size()==0,RES()); + + { + DVector<uint8_t>::Write w = data.write(); + f->get_buffer(w.ptr(),data.size()); + } + + Ref<DynamicFontData> dfd; + dfd.instance(); + dfd->set_font_data(data); + + if (r_error) + *r_error=OK; + + return dfd; +} + +void ResourceFormatLoaderDynamicFont::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("ttf"); +} + +bool ResourceFormatLoaderDynamicFont::handles_type(const String& p_type) const { + + return (p_type=="DynamicFontData"); +} + +String ResourceFormatLoaderDynamicFont::get_resource_type(const String &p_path) const { + + String el = p_path.extension().to_lower(); + if (el=="ttf") + return "DynamicFontData"; + return ""; +} + +#endif diff --git a/scene/resources/dynamic_font_stb.h b/scene/resources/dynamic_font_stb.h new file mode 100644 index 0000000000..6b72fb3703 --- /dev/null +++ b/scene/resources/dynamic_font_stb.h @@ -0,0 +1,178 @@ +#ifndef DYNAMICFONT_STB_H +#define DYNAMICFONT_STB_H + +#ifndef FREETYPE_ENABLED + +#include "font.h" +#include "stb_truetype.h" +#include "io/resource_loader.h" + + + +class DynamicFontAtSize; +class DynamicFont; + +class DynamicFontData : public Resource { + + OBJ_TYPE(DynamicFontData,Resource); + + bool valid; + + DVector<uint8_t> font_data; + DVector<uint8_t>::Read fr; + const uint8_t* last_data_ptr; + + struct KerningPairKey { + + union { + struct { + uint32_t A,B; + }; + + uint64_t pair; + }; + + _FORCE_INLINE_ bool operator<(const KerningPairKey& p_r) const { return pair<p_r.pair; } + }; + + Map<KerningPairKey,int> kerning_map; + + + Map<int,DynamicFontAtSize*> size_cache; + +friend class DynamicFontAtSize; + + stbtt_fontinfo info; + int ascent; + int descent; + int linegap; + + void lock(); + void unlock(); + +friend class DynamicFont; + + + Ref<DynamicFontAtSize> _get_dynamic_font_at_size(int p_size); +public: + + void set_font_data(const DVector<uint8_t>& p_font); + DynamicFontData(); + ~DynamicFontData(); +}; + + +class DynamicFontAtSize : public Reference { + + OBJ_TYPE(DynamicFontAtSize,Reference); + + + int rect_margin; + + struct CharTexture { + + DVector<uint8_t> imgdata; + int texture_size; + Vector<int> offsets; + Ref<ImageTexture> texture; + }; + + Vector<CharTexture> textures; + + struct Character { + + int texture_idx; + Rect2 rect; + float v_align; + float h_align; + float advance; + + Character() { texture_idx=0; v_align=0; } + }; + + + + HashMap< CharType, Character > char_map; + + _FORCE_INLINE_ void _update_char(CharType p_char); + +friend class DynamicFontData; + Ref<DynamicFontData> font; + float scale; + int size; + +protected: + +public: + + float get_height() const; + + float get_ascent() const; + float get_descent() const; + + Size2 get_char_size(CharType p_char,CharType p_next=0) const; + + float draw_char(RID p_canvas_item, const Point2& p_pos, const CharType& p_char,const CharType& p_next=0,const Color& p_modulate=Color(1,1,1)) const; + + + + DynamicFontAtSize(); + ~DynamicFontAtSize(); +}; + +/////////////// + +class DynamicFont : public Font { + + OBJ_TYPE( DynamicFont, Font ); + + Ref<DynamicFontData> data; + Ref<DynamicFontAtSize> data_at_size; + int size; + + +protected: + + static void _bind_methods(); + +public: + + void set_font_data(const Ref<DynamicFontData>& p_data); + Ref<DynamicFontData> get_font_data() const; + + void set_size(int p_size); + int get_size() const; + + virtual float get_height() const; + + virtual float get_ascent() const; + virtual float get_descent() const; + + virtual Size2 get_char_size(CharType p_char,CharType p_next=0) const; + + virtual bool is_distance_field_hint() const; + + virtual float draw_char(RID p_canvas_item, const Point2& p_pos, const CharType& p_char,const CharType& p_next=0,const Color& p_modulate=Color(1,1,1)) const; + + DynamicFont(); + ~DynamicFont(); + +}; + + + +///////////// + +class ResourceFormatLoaderDynamicFont : public ResourceFormatLoader { +public: + + virtual RES load(const String &p_path,const String& p_original_path="",Error *r_error=NULL); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String& p_type) const; + virtual String get_resource_type(const String &p_path) const; + +}; + + +#endif +#endif // DYNAMICFONT_H |