/* 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
sdragonx https://github.com/sdragonx