// Copyright (C) 2010, Guy Barrand. All rights reserved. // See the file tools.license for terms. #ifndef tools_sg_text #define tools_sg_text #include "nodekit" #include "back_area" #include "matrix" #include "text_hershey" #include "base_freetype" #include "enums" #include "rgba" #include "noderef" #include "mf" #include "../colorf" #include "../S_STRING" #include namespace tools { namespace sg { class text : public back_area { TOOLS_NODE(text,tools::sg::text,back_area) public: mf_string strings; sf confine; sf_vec color; sf_string font; sf_enum font_modeling; sf_string encoding; sf line_width; // for text_hershey. sf_enum front_face; //no more used. sf back_visible; sf enforce_front_height; sf front_height; sf enforce_front_width; sf front_width; sf wmargin_factor; sf hmargin_factor; sf_enum hjust; sf_enum vjust; public: virtual const desc_fields& node_desc_fields() const { TOOLS_FIELD_DESC_NODE_CLASS(tools::sg::text) static const desc_fields s_v(parent::node_desc_fields(),17, //WARNING : have the right count. TOOLS_ARG_FIELD_DESC(strings), TOOLS_ARG_FIELD_DESC(confine), TOOLS_ARG_FIELD_DESC(color), TOOLS_ARG_FIELD_DESC_OPTS_BEG(font,10) font_hershey().c_str(), font_lato_regular_ttf().c_str(), font_roboto_bold_ttf().c_str(), font_arial_ttf().c_str(), font_arialbd_ttf().c_str(), font_timesbd_ttf().c_str(), font_symbol_ttf().c_str(), font_stixgeneral_otf().c_str(), font_helvetica_ttf().c_str(), font_times_roman_ttf().c_str() TOOLS_ARG_FIELD_DESC_OPTS_END, TOOLS_ARG_FIELD_DESC_ENUMS_BEG(font_modeling,3) TOOLS_ARG_ENUM(font_outline), TOOLS_ARG_ENUM(font_filled), TOOLS_ARG_ENUM(font_pixmap) TOOLS_ARG_FIELD_DESC_ENUMS_END, TOOLS_ARG_FIELD_DESC(encoding), TOOLS_ARG_FIELD_DESC(line_width), TOOLS_ARG_FIELD_DESC_ENUMS_BEG(front_face,2) TOOLS_ARG_ENUM(winding_ccw), TOOLS_ARG_ENUM(winding_cw) TOOLS_ARG_FIELD_DESC_ENUMS_END, TOOLS_ARG_FIELD_DESC(back_visible), TOOLS_ARG_FIELD_DESC(enforce_front_height), TOOLS_ARG_FIELD_DESC(front_height), TOOLS_ARG_FIELD_DESC(enforce_front_width), TOOLS_ARG_FIELD_DESC(front_width), TOOLS_ARG_FIELD_DESC(wmargin_factor), TOOLS_ARG_FIELD_DESC(hmargin_factor), TOOLS_ARG_FIELD_DESC_ENUMS_BEG(hjust,3) TOOLS_ARG_ENUM(left), TOOLS_ARG_ENUM(center), TOOLS_ARG_ENUM(right) TOOLS_ARG_FIELD_DESC_ENUMS_END, TOOLS_ARG_FIELD_DESC_ENUMS_BEG(vjust,3) TOOLS_ARG_ENUM(bottom), TOOLS_ARG_ENUM(middle), TOOLS_ARG_ENUM(top) TOOLS_ARG_FIELD_DESC_ENUMS_END ); return s_v; } private: void add_fields(){ add_field(&strings); add_field(&confine); add_field(&color); add_field(&font); add_field(&font_modeling); add_field(&encoding); add_field(&line_width); add_field(&front_face); add_field(&back_visible); add_field(&enforce_front_height); add_field(&front_height); add_field(&enforce_front_width); add_field(&front_width); add_field(&wmargin_factor); add_field(&hmargin_factor); add_field(&hjust); add_field(&vjust); } public: virtual void render(render_action& a_action) { if(touched()) { update_sg(); reset_touched(); } if(back_visible.value()) m_back_sep.render(a_action); m_sep.render(a_action); } virtual void pick(pick_action& a_action) { if(touched()) { update_sg(); reset_touched(); } if(back_visible.value()) { nodekit_pick(a_action,m_back_sep,this); } } virtual void search(search_action& a_action) { if(touched()) { update_sg(); reset_touched(); } parent::search(a_action); if(a_action.done()) return; if(a_action.do_path()) a_action.path_push(this); if(back_visible.value()) { m_back_sep.search(a_action); if(a_action.done()) return; } m_sep.search(a_action); if(a_action.done()) return; if(a_action.do_path()) a_action.path_pop(); } virtual void bbox(bbox_action& a_action) { if(touched()) { update_sg(); reset_touched(); } if(back_visible.value()) m_back_sep.bbox(a_action); m_sep.bbox(a_action); } public: text(const base_freetype& a_ttf) :parent() ,strings() ,confine(false) ,color(colorf_black()) ,font(font_hershey()) ,font_modeling(font_filled) ,encoding(encoding_PAW()) ,line_width(1) ,front_face(winding_ccw) ,back_visible(true) ,enforce_front_height(false) ,front_height(1) ,enforce_front_width(false) ,front_width(1) ,wmargin_factor(0.9f) ,hmargin_factor(0.9f) ,hjust(left) // same default as base_text. ,vjust(middle) // not same default as base_text (which is bottom). We take middle for backcomp of confined text. // Note that text_hershey, text_freetype is (left,bottom) justified. ,m_base_text(0) ,m_TT_text(base_freetype::create(a_ttf)) { add_fields(); } virtual ~text(){ delete m_TT_text; } public: text(const text& a_from) :parent(a_from) ,strings(a_from.strings) ,confine(a_from.confine) ,color(a_from.color) ,font(a_from.font) ,font_modeling(a_from.font_modeling) ,encoding(a_from.encoding) ,line_width(a_from.line_width) ,front_face(a_from.front_face) ,back_visible(a_from.back_visible) ,enforce_front_height(a_from.enforce_front_height) ,front_height(a_from.front_height) ,enforce_front_width(a_from.enforce_front_width) ,front_width(a_from.front_width) ,wmargin_factor(a_from.wmargin_factor) ,hmargin_factor(a_from.hmargin_factor) ,hjust(a_from.hjust) ,vjust(a_from.vjust) ,m_base_text(0) ,m_TT_text(base_freetype::create(*a_from.m_TT_text)) { add_fields(); } text& operator=(const text& a_from){ parent::operator=(a_from); if(&a_from==this) return *this; strings = a_from.strings; confine = a_from.confine; color = a_from.color; font = a_from.font; font_modeling = a_from.font_modeling; encoding = a_from.encoding; line_width = a_from.line_width; front_face = a_from.front_face; back_visible = a_from.back_visible; enforce_front_height = a_from.enforce_front_height; front_height = a_from.front_height; enforce_front_width = a_from.enforce_front_width; front_width = a_from.front_width; wmargin_factor = a_from.wmargin_factor; hmargin_factor = a_from.hmargin_factor; hjust = a_from.hjust; vjust = a_from.vjust; m_base_text = 0; return *this; } public: float text_height() const { if(!m_base_text) return 0; return m_base_text->height; } bool is_empty() const { tools_vforcit(std::string,strings.values(),it) { const std::string& line = *it; if(line.size()) return false; } return true; } const separator& container() const {return m_back_sep;} //must be consistent with pick(). public: void update_sg() { parent::update_sg(); m_sep.clear(); m_base_text = 0; if(width.value()<=0) return; if(height.value()<=0) return; if(is_empty()) return; rgba* mat = new rgba(); mat->color = color; m_sep.add(mat); matrix* tsf = new matrix; m_sep.add(tsf); float zz = back_visible.value()?0.01f:0; if(font==font_hershey()) { draw_style* ds = new draw_style; ds->style = draw_lines; ds->line_width = line_width; m_sep.add(ds); text_hershey* _text = new text_hershey; m_base_text = _text; _text->encoding = encoding; _text->strings = strings; m_sep.add(_text); } else { m_base_text = m_TT_text; //ttf/arialbd 11 //01234567890 m_TT_text->font = font; m_TT_text->strings = strings; m_TT_text->modeling = font_modeling; m_sep.add(new noderef(*m_TT_text)); } if(enforce_front_height) { m_base_text->height = front_height; float mn_x,mn_y,mn_z; float mx_x,mx_y,mx_z; m_base_text->get_bounds(front_height,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z); float bxw = mx_x-mn_x; float xtrans = 0; if(hjust==center) { xtrans = 0; } else if(hjust==left) { xtrans = bxw*0.5f; } else if(hjust==right) { xtrans = -bxw*0.5f; } float bxh = mx_y-mn_y; float ytrans = 0; if(vjust==middle) { ytrans = 0; } else if(vjust==bottom) { ytrans = bxh*0.5f; } else if(vjust==top) { ytrans = -bxh*0.5f; } float xx = -(mn_x+mx_x)*0.5F+xtrans; float yy = -(mn_y+mx_y)*0.5F+ytrans; tsf->set_translate(xx,yy,zz); return; } if(enforce_front_width) { float fh = height * hmargin_factor; float th = fh; float mn_x,mn_y,mn_z; float mx_x,mx_y,mx_z; m_base_text->get_bounds(th,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z); float bxw = mx_x-mn_x; // adjust box width : // th -> bxw then to have front_width: if(bxw>0) { th = th*front_width/bxw; m_base_text->get_bounds(th,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z); } bxw = mx_x-mn_x; float xtrans = 0; if(hjust==center) { xtrans = 0; } else if(hjust==left) { xtrans = bxw*0.5f; } else if(hjust==right) { xtrans = -bxw*0.5f; } float bxh = mx_y-mn_y; float ytrans = 0; if(vjust==middle) { ytrans = 0; } else if(vjust==bottom) { ytrans = bxh*0.5f; } else if(vjust==top) { ytrans = -bxh*0.5f; } float xx = -(mn_x+mx_x)*0.5F+xtrans; float yy = -(mn_y+mx_y)*0.5F+ytrans; tsf->set_translate(xx,yy,zz); m_base_text->height = bxh; //=th? return; } //various automatic text height strategies : float fw = width * wmargin_factor; float fh = height * hmargin_factor; if(confine) { // code common to freetype and hershey : // try to adjust text within fw x fh (by attempting to be smart !). // the below assumes that text bounds are linear according // "text height". {float mn_x,mn_y,mn_z; float mx_x,mx_y,mx_z; float th = fh; m_base_text->get_bounds(th,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z); float bxh = mx_y-mn_y; // adjust box height : // fh -> bxh then to have fh : if(bxh>0) { th = fh*fh/bxh; m_base_text->get_bounds(th,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z); } //float bxw = box.mx()[0]-box.mn()[0]; float bxw = mx_x-mn_x; bxh = mx_y-mn_y; if((fh>0)&&(bxh>0)){ float fasp = fw/fh; float basp = bxw/bxh; if(fasp>=basp) { } else { // adjust box width : // th -> bxw then to have fw : if(bxw>0) { th = th*fw/bxw; m_base_text->get_bounds(th,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z); } } } m_base_text->height = th; //bxw = box.mx()[0]-box.mn()[0]; bxw = mx_x-mn_x; float xtrans = 0; if(hjust==center) { xtrans = 0; } else if(hjust==left) { xtrans = -fw*0.5f+bxw*0.5f; } else if(hjust==right) { xtrans = fw*0.5f-bxw*0.5f; } bxh = mx_y-mn_y; float ytrans = 0; if(vjust==middle) { ytrans = 0; } else if(vjust==bottom) { ytrans = -fh*0.5f+bxh*0.5f; } else if(vjust==top) { ytrans = fh*0.5f-bxh*0.5f; } float xx = -(mn_x+mx_x)*0.5F+xtrans; float yy = -(mn_y+mx_y)*0.5F+ytrans; tsf->set_translate(xx,yy,zz); } } else { // we arrange yy so that two aside texts // with same height will have their text base lines aligned. // The max height is given by ascent+descent (with descent>0). float th = fh; float mxh = m_base_text->ascent(th)+ m_base_text->y_advance(th)*(strings.size()-1)+ m_base_text->descent(th); if(mxh) th = fh*fh/mxh; //end/final height. m_base_text->height = th; //then all chars will fit into th. mxh = m_base_text->ascent(th)+ m_base_text->y_advance(th)*(strings.size()-1)+ m_base_text->descent(th); float yy = -fh*0.5f+m_base_text->descent(th)+ m_base_text->y_advance(th)*(strings.size()-1); {float mn_x,mn_y,mn_z; float mx_x,mx_y,mx_z; m_base_text->get_bounds(th,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z); float bxw = mx_x-mn_x; float xtrans = 0; if(hjust==center) { xtrans = 0; } else if(hjust==left) { xtrans = -fw*0.5f+bxw*0.5f; } else if(hjust==right) { xtrans = fw*0.5f-bxw*0.5f; } float xx = -(mn_x+mx_x)*0.5F+xtrans; tsf->set_translate(xx,yy,zz);} // truncate text at right if out of border : {std::vector labcut; tools_vforcit(std::string,strings.values(),it) { std::string scut; m_base_text->truncate(*it,th,fw,scut); labcut.push_back(std::move(scut)); } m_base_text->strings = labcut;} } } void map_back_area_to_text() { if(!m_base_text) return; float mn_x,mn_y,mn_z; float mx_x,mx_y,mx_z; m_base_text->get_bounds(front_height,mn_x,mn_y,mn_z,mx_x,mx_y,mx_z); float bxw = mx_x-mn_x; float bxh = mx_y-mn_y; parent::width = bxw/wmargin_factor; parent::height = bxh/hmargin_factor; } protected: separator m_sep; base_text* m_base_text; base_freetype* m_TT_text; //optimize : avoid too much freetype load font. }; }} #endif