/* glfont.hpp sdragonx 2019-08-15 00:03:33 opengl字體類,提供初學者參考學習 opengl初始化之后,創建字體 font.init(L"微軟雅黑", 32, 512); 然后在繪制函數里面添加以下測試代碼: //開啟2D模式,后面的800x600要根據窗口的實際客戶區大小設置,不然縮放之后效果不好 push_view2D(0, 0, 800, 600); wchar_t* str = L"abcdef字體繪制abcdef?123456ijk微軟雅黑"; font.color = vec4ub(255, 255, 255, 128); font.print(0, 0, str, wcslen(str)); font.tab_print(0, 32, L"abc\t123456\t7890", TEXT_MAX); wchar_t* tabled = L"abcdef字體繪制\tabc制表符\t123456"; font.color = vec4ub(255, 0, 0, 255); font.tab_print(0, 64, tabled, wcslen(tabled)); font.color = vec4ub(255, 0, 0, 255); font.draw_text(0, 200, 200, 400, str, wcslen(str), PT_LEFT); font.color = vec4ub(0, 255, 0, 255); font.draw_text(300, 200, 200, 400, str, wcslen(str), PT_CENTER); font.color = vec4ub(255, 0, 255, 255); font.draw_text(600, 200, 200, 400, str, wcslen(str), PT_RIGHT); pop_view(); //代碼結束 */ #ifndef GLFONT_HPP_20190815000333 #define GLFONT_HPP_20190815000333 #include <windows.h> #include <stdint.h> #include <map> #include <vector> //opengl頭文件,根據環境更改 #ifdef __glew_h__ #include <glew.h> #elif defined(__glad_h_) #include <glad.h> #else #include <gl/gl.h> #endif #if defined(__GNUC__) || defined(__clang__) #define TYPENAME typename #else #define TYPENAME #endif #define CGL_DEFAULT_FONT_SIZE 16 //默認字體大小 #define TEXT_MAX UINT32_MAX //0xFFFFFFFF namespace cgl{ #pragma pack(push, 1) //大紋理里面小圖塊結構 class teximage { public: typedef teximage this_type; public: intptr_t image; //紋理 uint16_t x, y, width, height; //小圖在大圖里面的位置信息 float u1, v1, u2, v2; //小圖的uv坐標信息 public: teximage():image(), x(), y(), width(), height(), u1(0.0f), v1(0.0f), u2(1.0f), v2(1.0f) { } this_type& operator=(const this_type& div) { image = div.image; x = div.x; y = div.y; width = div.width; height = div.height; u1 = div.u1; v1 = div.v1; u2 = div.u2; v2 = div.v2; return *this; } }; //字符信息 //如果一個字符需要輸出到left、top的位置,字符實際位置是left+x、top+y //輸出完畢之后,下一個字符的位置是left+next_x、top+next_y struct char_info { int16_t x; //字符偏移位置 int16_t y; int16_t next_x;//字符大小,下一字符偏移位置 int16_t next_y; }; //vec3<T> template<typename T> struct vec3 { T x, y, z; }; typedef vec3<int> vec3i; typedef vec3<float> vec3f; //vec4<T> template<typename T> struct vec4 { union{ T data[4]; struct{ T x, y, width, height; }; struct{ T red, green, blue, alpha; }; }; vec4() : x(), y(), width(), height(){/*void*/} vec4(T vx, T vy, T vw, T vh) : x(vx), y(vy), width(vw), height(vh){/*void*/} }; typedef vec4<int> vec4i; typedef vec4<float> vec4f; typedef vec4<BYTE> vec4ub; template<typename VT, typename TT, typename CT> struct vtx3t2c4 { typedef vtx3t2c4 this_type; typedef vec4<CT> color_type; VT x, y, z; TT u, v; color_type color; vtx3t2c4() : x(), y(), z(), u(), v(), color(){/*void*/} vtx3t2c4(const vec3<VT>& vtx, TT tu, TT tv, const color_type& cc) : x(v.x), y(v.y), z(v.z), u(tu), v(tv), color(cc) { /*void*/ } vtx3t2c4(VT vx, VT vy, VT vz, TT vu, TT vv, const color_type& cc) : x(vx), y(vy), z(vz), u(vu), v(vv), color(cc) { /*void*/ } this_type& operator=(const vec3<VT>& p) { x = p.x; y = p.y; z = p.z; return *this; } }; typedef vtx3t2c4<float, float, uint8_t> vtx3f; #pragma pack(pop) //--------------------------------------------------------------------------- //opengl的一些擴展函數 //2D視覺模式,如果你使用的是矩陣操作,把這里面的函數替換成矩陣操作 void push_view2D(int left, int top, int width, int height) { glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); /* #if CGL_COORDINATE == CGL_LOWER_LEFT //直角坐標系 this->ortho(left, width, top, height, 0, INT16_MAX); //重新設置正面,默認GL_CCW glFrontFace(GL_CCW); #else*/ //windows坐標系 glOrtho(left, left+width, top+height, top, 0, INT16_MAX); glFrontFace(GL_CW); //#endif //十字坐標系 //glOrtho(-width/2, width/2, -height/2, height/2, 0, INT_MAX);// //反轉屏幕 //glScalef(1.0f, -1.0f, 1.0f); //glTranslatef(0.0f, -height, 0.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.375f, 0.375f, 0.0f);//GL_POINTS and GL_LINES don't touch the right pixels glDisable(GL_DEPTH_TEST);//關閉深度測試 glDisable(GL_CULL_FACE); //關閉面剔除 } //還原視覺模式 void pop_view() { glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } //繪圖函數,這個根據使用的庫更改和優化 void vtx_begin(const vtx3f* vtx) { glVertexPointer(3, GL_FLOAT, sizeof(vtx3f), vtx); glTexCoordPointer(2, GL_FLOAT, sizeof(vtx3f), &vtx->u); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vtx3f), vtx->color.data); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_COLOR_ARRAY); } void vtx_end(const vtx3f* vtx) { glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_COLOR_ARRAY); } int draw_arrays(int shape, const vtx3f* vtx, size_t pos, size_t size) { vtx_begin(vtx); glDrawArrays(shape, pos, size); vtx_end(vtx); return 0; } //繪制圖片 int draw_image(intptr_t image, vec4ub color, float x, float y, float width, float height, float u1 = 0.0f, float v1 = 0.0f, float u2 = 1.0f, float v2 = 1.0f) { vtx3f vtx[] = { vtx3f(x, y, 0.0f, u1, v1, color), vtx3f(x + width, y, 0.0f, u2, v1, color), vtx3f(x + width, y + height, 0.0f, u2, v2, color), vtx3f(x , y + height, 0.0f, u1, v2, color) }; glBindTexture(GL_TEXTURE_2D, image); return draw_arrays(GL_TRIANGLE_FAN, vtx, 0, 4); } int draw_image(const teximage& image, vec4ub color, float x, float y, float width, float height) { return draw_image(image.image, color, x, y, width, height, image.u1, image.v1, image.u2, image.v2); } //--------------------------------------------------------------------------- //GDI字體封裝類 //有需要的,可以把這個類替換成freetype等其他字體庫 //實現方法不變,直接替換掉這個類就好 //比如我用freetype2實現一個ftFont的類 //或者用stb_font(一個輕量級freetype庫)實現一個stbFont類 class gdifont { private: HDC m_dc; //內存DC HFONT m_font; //字體句柄 GLYPHMETRICS m_gm; //字符模型信息 MAT2 m_mat; //轉置矩陣,默認初始矩陣 std::wstring m_ttfile;//用於保存單獨字體文件的路徑 std::vector<BYTE> m_pixelbuf;//用於保存字符像素信息 //std::vector<BYTE> m_fontResource;//內存字體 public: gdifont() : m_dc(NULL), m_font(NULL), m_ttfile(), m_pixelbuf() { //初始化字體轉置矩陣,默認初始矩陣 //這個矩陣可以實現字體的旋轉、偏移、縮放等效果 //2x2矩陣 //1 0 //0 1 m_mat.eM11.value = 1;m_mat.eM11.fract = 0; m_mat.eM12.value = 0;m_mat.eM12.fract = 0; m_mat.eM21.value = 0;m_mat.eM21.fract = 0; m_mat.eM22.value = 1;m_mat.eM22.fract = 0; } //字體句柄 HFONT handle()const { return m_font; } //創建字體 int create(const wchar_t* fontname, int size, int charset = GB2312_CHARSET) { //如果需要,首先釋放資源 if(this->handle()){ this->dispose(); } //創建內存DC m_dc = CreateCompatibleDC(0); //創建字體 m_font = CreateFontW( size, // logical height of font height 0, // logical average character width 0, // angle of escapement 0, // base-line orientation angle 0, // font weight 0, // italic attribute flag 0, // underline attribute flag 0, // strikeout attribute flag charset, // character set identifier 0, // output precision 0, // clipping precision DEFAULT_QUALITY, // output quality DEFAULT_PITCH | FF_SWISS, // pitch and family fontname // pointer to typeface name string ); //綁定字體到內存DC SelectObject(m_dc, m_font); return 0; } //加載單獨的字體文件 /*例如: font.load( "myfont.ttf", //字體文件,windows系統支持的字體,目錄可以是絕對路徑,也可以是相對路徑 "字體名稱", //點開字體文件,上面顯示的字體名稱,比如“微軟雅黑” 16, //字體大小 GB2312_CHARSET);//如果是中文字體,要設置中文字符集 */ int load(const wchar_t* filename, const wchar_t* fontname, int size, int charset = 0) { if(this->handle()){ this->dispose(); } m_ttfile = filename; AddFontResourceExW(m_ttfile.c_str(), FR_PRIVATE, 0); this->create(fontname, size, charset); return 0; } //加載內存、程序資源內的字體,這個暫時懶得實現了,有需要的可以查一下WINAPI實現 /* void load_memory(...) { FILE* f = fopen(filename, "rb"); fseek(f, 0, SEEK_END); m_fontResource.resize(ftell(f)); fseek(f, 0, SEEK_SET); fread(&m_fontResource[0], 1, m_fontResource.size(), f); fclose(f); DWORD dwFonts = 0; m_fontH = (HFONT)AddFontMemResourceEx(&m_fontResource[0], m_fontResource.size(), 0, &dwFonts); } */ //釋放資源 void dispose() { if(m_dc){ DeleteDC(m_dc); m_dc = null; } if(m_font){ DeleteObject(m_font); m_font = null; } if(!m_ttfile.empty()){ RemoveFontResourceExW(m_ttfile.c_str(), FR_PRIVATE, 0); m_ttfile.clear(); } //RemoveFontMemResourceEx(m_font); } //獲取一個字體的位圖和字符信息 int render_char(wchar_t ch, char_info& info) { //獲取字符位圖空間大小 int size = GetGlyphOutlineW(m_dc, ch, GGO_GRAY8_BITMAP, &m_gm, 0, NULL, &m_mat); //重新設置位圖緩沖區大小 m_pixelbuf.resize(size); //獲得字符位圖像素信息 GetGlyphOutlineW(m_dc, ch, GGO_GRAY8_BITMAP, &m_gm, 64*64, &m_pixelbuf[0], &m_mat); //GetGlyphOutline獲得的位圖像素是64階灰度,要轉換成256階灰度 //當然如果你要通過shader渲染,並希望獲得一些其他效果,可以不轉換,或進行其他轉換 gray256(); //填寫一下字符信息 info.x = m_gm.gmptGlyphOrigin.x; info.y = m_gm.gmptGlyphOrigin.y; info.next_x = m_gm.gmCellIncX; info.next_y = m_gm.gmBlackBoxY; return 0; } //位圖寬度 int width()const { return m_gm.gmBlackBoxX; } //位圖高度 int height()const { return m_gm.gmBlackBoxY; } //位圖像素數據 void* data() { return &m_pixelbuf[0]; } //64階灰度轉256階灰度 void gray256() { BYTE* p = &m_pixelbuf[0]; int c; //數據行是四字節對齊的 DWORD linewidth = (m_gm.gmBlackBoxX + 3) & 0xFFFFFFFC; for(size_t y=0; y<m_gm.gmBlackBoxY; ++y){ for(size_t x = 0; x < m_gm.gmBlackBoxX; ++x){ c = p[x]; c *= 4;//64x4 = 256 if(c > 255)c = 255;//約束在0~255范圍之內 p[x] = c; } p += linewidth;//移動到下一行 } } //測試獲取的位圖,畫到一個HDC上面 #ifdef _DEBUG void paint(HDC dc) { BYTE* p = &m_pixelbuf[0]; int c; //數據行是四字節對齊的 DWORD linewidth = (m_gm.gmBlackBoxX + 3) & 0xFFFFFFFC; for(size_t y=0; y<m_gm.gmBlackBoxY; ++y){ for(size_t x = 0; x < m_gm.gmBlackBoxX; ++x){ c = p[x]; SetPixelV(dc, x, y, RGB(c, c, c)); } p += linewidth; } } #endif }; //--------------------------------------------------------------------------- //imagelist 圖集類,自動把小圖拼成圖集 // //這個簡單的圖集類,用於拼合高度大小變化不大的圖片,比如圖標 // //有需要的,可以github搜索maxrects庫,可以把不同大小的字體拼成一個大圖 //一般使用固定大小的字體,因為字符位圖大小相對變化不大,空間浪費也不算太大 //使用一個高度(字體高度)作為每一行高度,每添加進一個小圖,x方向向右偏移一個位置 //到達右邊邊界,換行。 //如果到達紋理右下角,則自動添加一個紋理頁 template<typename T, typename U = int> class imagelist { public: struct ITEM { teximage image; U data; }; typedef const ITEM* item_type; typedef typename std::map<T, ITEM> map_type; typedef typename map_type::iterator iterator; typedef typename map_type::const_iterator const_iterator; private: std::vector<GLuint> m_texlist; //保存的紋理頁 map_type m_itemlist; //小圖信息列表,使用std::map組織,也可以根據需要用數組組織 GLenum m_format;//紋理格式 int m_width; //紋理大小 int m_height; int m_filter; //紋理過濾方式 int m_size; //小圖塊大小,只記錄高度 int m_u, m_v; //當前小圖塊添加位置 public: imagelist():m_texlist(), m_itemlist(), m_format(GL_RGBA), m_filter(GL_LINEAR), m_width(512), m_height(512), m_size(16), m_u(0), m_v(0) { } ~imagelist() { this->dispose(); } //初始化創建圖集 int create(size_t width, size_t height, size_t size, GLenum format = GL_RGBA, GLenum filter = GL_LINEAR) { this->dispose(); m_width = width; m_height = height; m_format = format, m_size = size; m_filter = filter; m_u = m_v = 0; return 0; } //釋放資源 void dispose() { m_itemlist.clear(); if(!m_texlist.empty()){ //刪除所有紋理頁 glDeleteTextures(m_texlist.size(), &m_texlist[0]); m_texlist.clear(); } m_u = m_v = 0; } //當前緩存的小圖塊數量 size_t size()const { return m_imglist.size(); } //添加一個小圖塊 int insert(const T& index, int width, int height, GLenum format, void* data, const U& userdata) { //首先移動坐標位置 position_move(width); //綁定當前紋理,也就是圖集的最后一個 glBindTexture(GL_TEXTURE_2D, m_texlist.back()); //更新紋理局部像素 glTexSubImage2D(GL_TEXTURE_2D, 0, m_u, m_v, width, std::min(m_size, height), format, GL_UNSIGNED_BYTE, data); //保存信息 ITEM item; item.image.image = m_texlist.back(); item.image.x = m_u; item.image.y = m_v; item.image.width = width; item.image.height = std::min(height, m_size); item.image.u1 = float(m_u) / m_width; item.image.v1 = float(m_v) / m_height; item.image.u2 = float(m_u+width)/m_width; item.image.v2 = float(m_v+std::min(m_size, height))/m_height; item.data = userdata; m_itemlist[index] = item; //x方向移動坐標 m_u += width + 1;//做一個像素的間距 return index; } //查詢圖塊信息 item_type items(const T& index)const { const_iterator itr = m_itemlist.find(index); if(itr != m_itemlist.end()){ return &itr->second; } else{ return null; } } //查詢圖塊是否存在 bool exist(const T& index)const { return items(index); } private: void position_move(int width) { GLuint tex = 0; width += 1;//做一個像素的間距 if(m_u + width > m_width){//換行 m_u = 0; m_v += m_size + 1; } //創建新紋理頁 if(m_texlist.empty() || m_v + m_size > m_height){ glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); glTexImage2D(GL_TEXTURE_2D, 0, m_format, m_width, m_height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_filter); m_texlist.push_back(tex); m_u = 0; m_v = 0; } } teximage* BindTexture(int index) { iterator itr = m_imglist.find(index); if(itr!=m_imglist.end()) { glBindTexture(GL_TEXTURE_2D, itr->second.image.image); return &itr->second.image; } return NULL; } }; //--------------------------------------------------------------------------- //glfont 字體類 // //gles對GL_ALPHA8支持貌似不好,可以替換成GL_RGBA(需要將256灰度轉換成RGBA格式) //或者使用GL_RED等單通道格式,通過shader渲染字體 // //draw_text字符串繪制參數 #define PT_LEFT 0x00000000 #define PT_RIGHT 0x00000001 #define PT_CENTER 0x00000002 #define PT_SHADOW 0x00010000 #define PT_CALCRECT 0x80000000 class glfont { public: typedef imagelist<wchar_t, char_info> imagelist_type; typedef TYPENAME imagelist_type::item_type char_item; //const static int SHADOW_SIZE = 2; //陰影大小,帶陰影的字體,這個實現代碼太長 enum{ TEXTURE_SIZE = 1024, //默認紋理大小 TEX_FORMAT = GL_ALPHA8, //PC默認使用GL_ALPHA8紋理格式 SRC_FORMAT = GL_ALPHA, //位圖數據默認格式 TAB_WIDTH = 4 //制表符寬度 }; struct PT_WORD { const wchar_t* begin; const wchar_t* end; size_t width; }; private: gdifont m_font; //字體類,可以替換成其他字體類 std::wstring m_name;//字體名字 int m_size; //字體大小 imagelist_type m_imagelist;//圖集類 public: vec4ub color; //字體顏色 public: glfont() : m_font(), m_name(), m_size(), m_imagelist(), color(255, 255, 255, 255) { } void init(const wchar_t* fontname, int size = CGL_DEFAULT_FONT_SIZE, int texture_size = TEXTURE_SIZE) { m_name = fontname; m_size = size; //m_imagelist = imagelist; m_font.create(fontname, size); m_imagelist.create(texture_size, texture_size, size, TEX_FORMAT, GL_NEAREST); } void clear() { m_imagelist.dispose(); } void dispose() { m_imagelist.dispose(); m_size = 0; } //獲得字符item char_item char_items(wchar_t ch) { if(!m_imagelist.exist(ch)){ make_char(ch); } return m_imagelist.items(ch); } //獲得字符寬度 int char_width(wchar_t ch) { char_item item; if(!m_imagelist.exist(ch)){ make_char(ch); } item = m_imagelist.items(ch); return item ? item->data.next_x : 0; } //獲取字符高度 int char_height() { return m_size; } //獲取字符串寬度 int text_width(const wchar_t* text, size_t length) { int width = 0; for(size_t i=0; i<length; ++i){ width += char_width(text[i]); } return width; } //輸出一個字符 int put_char(int x, int y, wchar_t ch, int flag = 0) { char_item item = m_imagelist.items(ch); if(!item){ make_char(ch); item = m_imagelist.items(ch); } draw_image(item->image, color, x + item->data.x, y + m_size - item->data.y, item->image.width, item->image.height); return item->data.next_x; } //繪制一行字體,不支持制表符 int print(int x, int y, const wchar_t* text, size_t length) { if(length == TEXT_MAX)length = wcslen(text); for(size_t i=0; i<length; ++i){ x += put_char(x, y, text[i]); } return 0; } //繪制一行字體,支持制表符 int tab_print(int x, int y, const wchar_t* text, size_t length) { int tab = TAB_WIDTH * (this->char_height() >> 1); if(length == TEXT_MAX)length = wcslen(text); for(size_t i=0; i<length; ++i) { if(text[i] == '\t'){ x = align_next(x, tab); } else{ x += put_char(x, y, text[i]); } } return 0; } //仿GDI函數DrawText int draw_text( int left, int top, int width, int height, const wchar_t* text, size_t length, int style); private: int make_char(wchar_t ch); //根據當前位置n,計算下一個tab的對齊位置 next_tab_position int align_next(int n, int align) { n = n - n % align + align; return n; } int get_tabled_line(const wchar_t* &l_end, const wchar_t* end, int width); }; //緩存一個字符 int glfont::make_char(wchar_t ch) { char_info info; //數據行4字節對齊 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); //渲染字符 m_font.render_char(ch, info); //添加到圖集 return m_imagelist.insert(ch, m_font.width(), m_font.height(), GL_ALPHA, m_font.data(), info); } //返回下一個字符的繪制位置-1表示換行 int glfont::get_tabled_line(const wchar_t* &l_end, const wchar_t* end, int width) { int tab = TAB_WIDTH * (this->char_height() >> 1); int n;// = 0; int l_width = 0; for(; l_end < end; ++l_end){ if(*l_end == '\r'){ continue; } else if(*l_end == '\n'){//next line break; } //else if(*l_end == ' ')//add word else if(*l_end == '\t'){ n = align_next(l_width, tab); if(n < width){ l_width = n; } else{ //break; return l_width; } } else{ n = this->char_width(*l_end); if(l_width + n < width){ l_width += n; } else{//next line //break; return l_width; } } } return l_width; } int glfont::draw_text(int left, int top, int width, int height, const wchar_t* text, size_t length, int style) { int px = 0, py = top; //字符繪制位置 //int chwidth = 0; //字符寬度 int ch_size = this->char_height(); if(length == TEXT_MAX)length = wcslen(text); int tab = TAB_WIDTH * (ch_size >> 1); //vec4ub c = dc->color; const wchar_t* end = text + length; const wchar_t* l_begin; const wchar_t* l_end = text; int l_width = 0; std::vector<PT_WORD> words; int x = 0; while(l_end < end) { //l_width = 0; l_begin = l_end; //word_begin = l_end; //get line l_width = get_tabled_line(l_end, end, width); px = left; if(style & PT_RIGHT){ px += width - l_width; } else if(style & PT_CENTER){ px += (width - l_width) / 2; } if(l_begin != l_end && !(style & PT_CALCRECT)){ if(style & PT_SHADOW){ //dc->color = shadow_color; //draw_shadow(dc, px, py, text+begin, end-begin); } //dc->color = c; x = 0; for(; l_begin < l_end; ++l_begin) { if(*l_begin == '\t'){ x = align_next(x, tab); continue; } else if(*l_begin == '\r' || *l_begin == '\n'){ continue; } x += put_char(px + x, py, *l_begin); } } if(*l_end == '\n'){//next line ++l_end; } py += ch_size + (ch_size / 8);// 1/8 line space if(int(top + height) < py + ch_size){ break; } } return py - top; } }//end namespace cgl #endif //GLFONT_HPP_20190815000333