diff --git a/examples/cpu/bubblechart.cpp b/examples/cpu/bubblechart.cpp index c03d5943..0f7b2986 100644 --- a/examples/cpu/bubblechart.cpp +++ b/examples/cpu/bubblechart.cpp @@ -77,9 +77,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/cpu/fractal.cpp b/examples/cpu/fractal.cpp index 935ad617..afb397f4 100644 --- a/examples/cpu/fractal.cpp +++ b/examples/cpu/fractal.cpp @@ -42,9 +42,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/cpu/histogram.cpp b/examples/cpu/histogram.cpp index a75ccddd..1acf6e1a 100644 --- a/examples/cpu/histogram.cpp +++ b/examples/cpu/histogram.cpp @@ -59,9 +59,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/cpu/plot3.cpp b/examples/cpu/plot3.cpp index 5e8b6340..c135a722 100644 --- a/examples/cpu/plot3.cpp +++ b/examples/cpu/plot3.cpp @@ -49,9 +49,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/cpu/plotting.cpp b/examples/cpu/plotting.cpp index 2ab3655f..2cc86501 100644 --- a/examples/cpu/plotting.cpp +++ b/examples/cpu/plotting.cpp @@ -55,9 +55,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/cpu/surface.cpp b/examples/cpu/surface.cpp index c48ccddc..4979f5fc 100644 --- a/examples/cpu/surface.cpp +++ b/examples/cpu/surface.cpp @@ -53,9 +53,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/cuda/fractal.cu b/examples/cuda/fractal.cu index 9e8e46c1..f4f03ce3 100644 --- a/examples/cuda/fractal.cu +++ b/examples/cuda/fractal.cu @@ -26,9 +26,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/cuda/histogram.cu b/examples/cuda/histogram.cu index 0abb3e66..238ee1aa 100644 --- a/examples/cuda/histogram.cu +++ b/examples/cuda/histogram.cu @@ -78,9 +78,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/cuda/plot3.cu b/examples/cuda/plot3.cu index eaad66c4..7966216c 100644 --- a/examples/cuda/plot3.cu +++ b/examples/cuda/plot3.cu @@ -41,9 +41,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/cuda/plotting.cu b/examples/cuda/plotting.cu index a9824da2..0ebb5883 100644 --- a/examples/cuda/plotting.cu +++ b/examples/cuda/plotting.cu @@ -35,9 +35,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/cuda/surface.cu b/examples/cuda/surface.cu index 05df0c12..562836d1 100644 --- a/examples/cuda/surface.cu +++ b/examples/cuda/surface.cu @@ -35,9 +35,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/opencl/fractal.cpp b/examples/opencl/fractal.cpp index 65d6da98..10264f29 100644 --- a/examples/opencl/fractal.cpp +++ b/examples/opencl/fractal.cpp @@ -118,9 +118,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/opencl/histogram.cpp b/examples/opencl/histogram.cpp index 424d532c..1ff9ea2a 100644 --- a/examples/opencl/histogram.cpp +++ b/examples/opencl/histogram.cpp @@ -213,9 +213,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/opencl/plot3.cpp b/examples/opencl/plot3.cpp index d312e002..a0effd09 100644 --- a/examples/opencl/plot3.cpp +++ b/examples/opencl/plot3.cpp @@ -87,9 +87,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/opencl/plotting.cpp b/examples/opencl/plotting.cpp index 60d823e9..29724142 100644 --- a/examples/opencl/plotting.cpp +++ b/examples/opencl/plotting.cpp @@ -90,9 +90,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/examples/opencl/surface.cpp b/examples/opencl/surface.cpp index 3b508499..cc0dc756 100644 --- a/examples/opencl/surface.cpp +++ b/examples/opencl/surface.cpp @@ -93,9 +93,9 @@ int main(void) * it can be used for rendering text */ fg::Font fnt; #ifdef OS_WIN - fnt.loadSystemFont("Calibri", 32); + fnt.loadSystemFont("Calibri"); #else - fnt.loadSystemFont("Vera", 32); + fnt.loadSystemFont("Vera"); #endif wnd.setFont(&fnt); diff --git a/include/fg/defines.h b/include/fg/defines.h index b9cb394e..a77b2d0c 100644 --- a/include/fg/defines.h +++ b/include/fg/defines.h @@ -92,6 +92,11 @@ enum ErrorCode { * */ FG_ERR_NOT_SUPPORTED = 5001, ///< Feature not supported FG_ERR_NOT_CONFIGURED = 5002, ///< Library configuration mismatch + /* + * Font config related error codes + * '6**' + * */ + FG_ERR_FONTCONFIG_ERROR = 6001, ///< Fontconfig related error /* * other error codes * match the following pattern diff --git a/include/fg/font.h b/include/fg/font.h index c34b3942..69569d4d 100644 --- a/include/fg/font.h +++ b/include/fg/font.h @@ -49,17 +49,15 @@ class Font { Load a given font file \param[in] pFile True Type Font file path - \param[in] pFontSize the size of the font glyphs that will be created */ - FGAPI void loadFont(const char* const pFile, int pFontSize); + FGAPI void loadFontFile(const char* const pFile); /** Load a system font based on the name \param[in] pName True Type Font name - \param[in] pFontSize the size of the font glyphs that will be created */ - FGAPI void loadSystemFont(const char* const pName, int pFontSize); + FGAPI void loadSystemFont(const char* const pName); /** Get handle for internal implementation of Font object diff --git a/src/chart.cpp b/src/chart.cpp index 55b67017..064ffc59 100644 --- a/src/chart.cpp +++ b/src/chart.cpp @@ -33,7 +33,7 @@ using namespace std; typedef std::vector::const_iterator StringIter; -static const int CHART2D_FONT_SIZE = 15; +static const int CHART2D_FONT_SIZE = 16; const std::shared_ptr& getChartFont() { @@ -42,9 +42,9 @@ const std::shared_ptr& getChartFont() std::call_once(flag, []() { #if defined(OS_WIN) - mChartFont.loadSystemFont("Calibri", 32); + mChartFont.loadSystemFont("Calibri"); #else - mChartFont.loadSystemFont("Vera", 32); + mChartFont.loadSystemFont("Vera"); #endif }); @@ -98,7 +98,7 @@ void AbstractChart::renderTickLabels( * text center with tick mark position */ if(pCoordsOffset < mTickCount) { // offset for y axis labels - pos[0] -= (CHART2D_FONT_SIZE*it->length()/2.0f+mTickSize/2); + pos[0] -= (CHART2D_FONT_SIZE*it->length()/2.0f+mTickSize); }else if(pCoordsOffset >= mTickCount && pCoordsOffset < 2*mTickCount) { // offset for x axis labels pos[0] -= (CHART2D_FONT_SIZE*it->length()/4.0f); @@ -430,7 +430,7 @@ void chart2d_impl::render(const int pWindowId, /* render chart axes titles */ if (!mYTitle.empty()) { glm::vec4 res = trans * glm::vec4(-1.0f, 0.0f, 0.0f, 1.0f); - pos[0] = 2; + pos[0] = 4; pos[1] = h*(res.y+1.0f)/2.0f; fonter->render(pWindowId, pos, WHITE, mYTitle.c_str(), CHART2D_FONT_SIZE, true); } diff --git a/src/common.cpp b/src/common.cpp index 36d7ea8f..5d2370a7 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -158,11 +158,6 @@ GLuint initShaders(const char* pVertShaderSrc, const char* pFragShaderSrc) return shaderProgram; } -int nextP2(const int pValue) -{ - return int(std::pow(2, (std::ceil(std::log2(pValue))))); -} - float clampTo01(const float pValue) { return (pValue < 0.0f ? 0.0f : (pValue>1.0f ? 1.0f : pValue)); diff --git a/src/common.hpp b/src/common.hpp index 543fb09c..167e27b4 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -86,12 +86,6 @@ GLuint createBuffer(GLenum pTarget, size_t pSize, const T* pPtr, GLenum pUsage) return retVal; } -/* compute the next power of two after given integer - * - * @pValue is the value whose next power of two is to be computed - */ -int nextP2(const int pValue); - #ifdef OS_WIN /* Get the paths to font files in Windows system directory * @@ -134,7 +128,6 @@ std::ostream& operator<<(std::ostream&, const glm::mat4&); glm::vec3 trackballPoint(const float pX, const float pY, const float pWidth, const float pHeight); - namespace internal { diff --git a/src/font.cpp b/src/font.cpp index 82ba0d9a..98587240 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -14,6 +14,8 @@ #include #include +#include +#include #include #include @@ -22,6 +24,17 @@ #include #include FT_FREETYPE_H +#include FT_STROKER_H + +#undef __FTERRORS_H__ +#define FT_ERRORDEF( e, v, s ) { e, s }, +#define FT_ERROR_START_LIST { +#define FT_ERROR_END_LIST { 0, 0 } }; +static const struct { + int code; + const char* message; +} FT_Errors[] = +#include FT_ERRORS_H #ifndef OS_WIN #include @@ -32,98 +45,188 @@ #include #endif -/* freetype library types */ -static FT_Library gFTLib; -static FT_Face gFTFace; +#define START_CHAR 32 +#define END_CHAR 126 -static const int START_CHAR = 32; -static const int END_CHAR = 127; +/* freetype library types */ namespace internal { -#define FT_THROW_ERROR(msg, err) \ +#ifdef NDEBUG +/* Relase Mode */ +#define FT_THROW_ERROR(msg, error) \ throw fg::Error("Freetype library", __LINE__, msg, err); -void font_impl::extractGlyph(int pCharacter) +#else +/* Debug Mode */ +#define FT_THROW_ERROR(msg, err) \ + do { \ + std::ostringstream ss; \ + ss << "FT_Error (0x"<< std::hex << FT_Errors[bError].code <<") : " \ + << FT_Errors[bError].message << std::endl; \ + throw fg::Error(ss.str().c_str(), __LINE__, msg, err); \ + } while(0); + +#endif + +void font_impl::loadAtlasWithGlyphs(const size_t pFontSize) { - FT_Load_Glyph(gFTFace, FT_Get_Char_Index(gFTFace, pCharacter), FT_LOAD_DEFAULT); - FT_Render_Glyph(gFTFace->glyph, FT_RENDER_MODE_NORMAL); - FT_Bitmap& bitmap = gFTFace->glyph->bitmap; - - int pIndex = pCharacter - START_CHAR; - - int bmp_w = bitmap.width; - int bmp_h = bitmap.rows; - int w = nextP2(bmp_w); - int h = nextP2(bmp_h); - - std::vector glyphData(w*h, 0); - for (int j=0; jglyph, &currGlyph); + if (bError) { + FT_Done_Face(face); + FT_Done_FreeType(library); + FT_THROW_ERROR("FT_Get_Glyph", fg::FG_ERR_FREETYPE_ERROR); + } + + //FIXME Renable when outline strokes are working + ///* use stroker to get outline */ + //FT_Stroker stroker; + //bError = FT_Stroker_New(library, &stroker); + //if (bError) { + // FT_Stroker_Done(stroker); + // FT_Done_Face(face); + // FT_Done_FreeType(library); + // FT_THROW_ERROR("FT_Stroker_New", fg::FG_ERR_FREETYPE_ERROR); + //} + + //FT_Stroker_Set(stroker, (int)(0.75*64), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); + + ///* stroke the outline to current glyph */ + //bError = FT_Glyph_StrokeBorder( &currGlyph, stroker, 0, 1 ); + ////bError = FT_Glyph_Stroke(&currGlyph, stroker, 1); + //if (bError) { + // FT_Stroker_Done(stroker); + // FT_Done_Face(face); + // FT_Done_FreeType(library); + // FT_THROW_ERROR("FT_Glyph_Stroke", fg::FG_ERR_FREETYPE_ERROR); + //} + //FT_Stroker_Done(stroker); + + /* fixed channel depth of 1 */ + bError = FT_Glyph_To_Bitmap(&currGlyph, FT_RENDER_MODE_NORMAL, 0, 1); + if (bError) { + //FIXME Renable when outline strokes are working + //FT_Stroker_Done(stroker); + FT_Done_Face(face); + FT_Done_FreeType(library); + FT_THROW_ERROR("FT_Glyph_To_Bitmap", fg::FG_ERR_FREETYPE_ERROR); + } + + FT_BitmapGlyph bmpGlyph = (FT_BitmapGlyph) currGlyph; + FT_Bitmap bmp = bmpGlyph->bitmap; + + int w = bmp.width + 1; + int h = bmp.rows + 1; + + glm::vec4 region = mAtlas->getRegion(w, h); + + if (region.x<0 || region.y<0) { + missed++; + std::cerr<<"Texture atlas is full"<setRegion(x, y, w, h, bmp.buffer, bmp.pitch); + + Glyph* glyph = new Glyph(); + + glyph->mWidth = w; + glyph->mHeight = h; + + glyph->mBearingX = face->glyph->metrics.horiBearingX>>6; + glyph->mBearingY = face->glyph->metrics.horiBearingY>>6; + + glyph->mAdvanceX = face->glyph->advance.x>>6; + glyph->mAdvanceY = (face->glyph->metrics.height - face->glyph->metrics.horiBearingY)>>6; + + glyph->mS0 = x/(float)mAtlas->width(); + glyph->mT1 = y/(float)mAtlas->height(); + glyph->mS1 = (x + glyph->mWidth)/(float)mAtlas->width(); + glyph->mT0 = (y + glyph->mHeight)/(float)mAtlas->height(); - CheckGL("Before Character texture creation"); - // texture from it - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glGenTextures(1, &(mCharTextures[pIndex])); - glBindTexture(GL_TEXTURE_2D, mCharTextures[pIndex]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, - w, h, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, - (pCharacter==32 ? NULL : &glyphData.front())); - glGenerateMipmap(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, 0); - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - CheckGL("After Character texture creation"); - - mAdvX[pIndex] = gFTFace->glyph->advance.x>>6; - mBearingX[pIndex] = gFTFace->glyph->metrics.horiBearingX>>6; - mCharWidth[pIndex] = gFTFace->glyph->metrics.width>>6; - - mAdvY[pIndex] = (gFTFace->glyph->metrics.height - gFTFace->glyph->metrics.horiBearingY)>>6; - mBearingY[pIndex] = gFTFace->glyph->metrics.horiBearingY>>6; - mCharHeight[pIndex] = gFTFace->glyph->metrics.height>>6; - - mNewLine = std::max(mNewLine, int(gFTFace->glyph->metrics.height>>6)); - - // Rendering data, texture coordinates are always the same, - // so now we waste a little memory - float quad[8]; - float quad_texcoords[8] = { 0, 1, 0, 0, 1, 1, 1, 0}; - - quad[0] = 0.0f; quad[1] = float(-mAdvY[pIndex]+h); - quad[2] = 0.0f; quad[3] = float(-mAdvY[pIndex]); - quad[4] = float(w); quad[5] = float(-mAdvY[pIndex]+h); - quad[6] = float(w); quad[7] = float(-mAdvY[pIndex]); - - // texture coordinates are duplicated for each character - // TODO work on removing this duplicates to reduce memory usage - for (int i=0; i<4; ++i) { - float* vert_ptr = quad+2*i; - float* tex_ptr = quad_texcoords+2*i; - mVertexData.insert(mVertexData.end(), vert_ptr, vert_ptr+2); - mVertexData.insert(mVertexData.end(), tex_ptr, tex_ptr+2); + currList.push_back(glyph); + + FT_Done_Glyph(currGlyph); } + + /* cleanup freetype variables */ + FT_Done_Face(face); + FT_Done_FreeType(library); } void font_impl::bindResources(int pWindowId) { if (mVAOMap.find(pWindowId) == mVAOMap.end()) { - GLsizei size = sizeof(glm::vec2); - GLuint vao = 0; - /* create a vertex array object - * with appropriate bindings */ + size_t sz = 2*sizeof(float); + GLuint vao; glGenVertexArrays(1, &vao); glBindVertexArray(vao); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, mVBO); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, size*2, 0); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, size*2, reinterpret_cast(size)); - glBindVertexArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sz, 0); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2*sz, reinterpret_cast(sz)); /* store the vertex array object corresponding to * the window instance in the map */ mVAOMap[pWindowId] = vao; @@ -138,41 +241,54 @@ void font_impl::unbindResources() const void font_impl::destroyGLResources() { - if (mIsFontLoaded) { - if (mProgram) glDeleteProgram(mProgram); - if (mVBO) glDeleteBuffers(1, &mVBO); - glDeleteTextures(NUM_CHARS, mCharTextures); + if (mVBO) + glDeleteBuffers(1, &mVBO); + /* remove all glyph structures from heap */ + for (auto it: mGlyphLists) { + /* for each font size glyph list */ + for (auto& m : it) { + delete m; /* delete Glyph structure */ + } + it.clear(); } - if (mProgram) glDeleteProgram(mProgram); - if (mSampler) glDeleteSamplers(1, &mSampler); + /* clear list */ + mGlyphLists.clear(); } font_impl::font_impl() - : mIsFontLoaded(false), mTTFfile(""), - mVBO(0), mProgram(0), mSampler(0) + : mTTFfile(""), mIsFontLoaded(false), mAtlas(new FontAtlas(1024, 1024, 1)), + mVBO(0), mProgram(0), mOrthoW(1), mOrthoH(1) { - mProgram = initShaders(glsl::font_vs.c_str(), glsl::font_fs.c_str()); - memset(mCharTextures, 0, NUM_CHARS); - glGenSamplers(1, &mSampler); - glSamplerParameteri(mSampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glSamplerParameteri(mSampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glSamplerParameteri(mSampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glSamplerParameteri(mSampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + mProgram = initShaders(glsl::font_vs.c_str(), glsl::font_fs.c_str()); + mPMatIndex = glGetUniformLocation(mProgram, "projectionMatrix"); + mMMatIndex = glGetUniformLocation(mProgram, "modelViewMatrix"); + mTexIndex = glGetUniformLocation(mProgram, "tex"); + mClrIndex = glGetUniformLocation(mProgram, "textColor"); + + mGlyphLists.resize(MAX_FONT_SIZE-MIN_FONT_SIZE+1, GlyphList()); } font_impl::~font_impl() { destroyGLResources(); + if (mProgram) glDeleteProgram(mProgram); } void font_impl::setOthro2D(int pWidth, int pHeight) { - mWidth = pWidth; - mHeight= pHeight; + mOrthoW = pWidth; + mOrthoH = pHeight; + mProjMat = glm::ortho(0.0f, float(mOrthoW), 0.0f, float(mOrthoH)); } -void font_impl::loadFont(const char* const pFile, int pFontSize) +void font_impl::loadFont(const char* const pFile) { + CheckGL("Begin font_impl::loadFont"); + + /* Check if font is already loaded. If yes, check if current font load + * request is same as earlier. If so, return from the function, otherwise, + * cleanup currently used resources and mark accordingly for subsequent + * font loading.*/ if (mIsFontLoaded) { if (pFile==mTTFfile) return; @@ -181,54 +297,73 @@ void font_impl::loadFont(const char* const pFile, int pFontSize) mIsFontLoaded = false; } } - mLoadedPixelSize = pFontSize; - CheckGL("Begin Font::loadFont"); - // Initialize freetype font library - FT_Error bError = FT_Init_FreeType(&gFTLib); - - bError = FT_New_Face(gFTLib, pFile, 0, &gFTFace); - if(bError) { - FT_Done_FreeType(gFTLib); - FT_THROW_ERROR("font face creation failed", fg::FG_ERR_FREETYPE_ERROR); + mTTFfile = pFile; + /* Load different font sizes into font atlas */ + for (size_t s=MIN_FONT_SIZE; s<=MAX_FONT_SIZE; ++s) { + loadAtlasWithGlyphs(s); } - bError = FT_Set_Pixel_Sizes(gFTFace, 0, pFontSize); - if (bError) { - FT_Done_Face(gFTFace); - FT_Done_FreeType(gFTLib); - FT_THROW_ERROR("set font size failed", fg::FG_ERR_FREETYPE_ERROR); - } + mAtlas->upload(); + + /* push each glyphs vertex and texture data into VBO */ + std::vector vdata; + + uint index = 0; + + for (size_t f=0; f data(16, 0.0f); + data[0] = 0.0f; data[1] = float(-g->mAdvanceY+g->mHeight); + data[2] = g->mS0; data[3] = g->mT1; - mVBO = createBuffer(GL_ARRAY_BUFFER, mVertexData.size(), &(mVertexData.front()), GL_STATIC_DRAW); + data[4] = 0.0f; data[5] = float(-g->mAdvanceY); + data[6] = g->mS0; data[7] = g->mT0; + + data[8] = float(g->mWidth); data[9] = float(-g->mAdvanceY+g->mHeight); + data[10] = g->mS1; data[11] = g->mT1; + + data[12] = float(g->mHeight); data[13] = float(-g->mAdvanceY); + data[14] = g->mS1; data[15] = g->mT0; + + vdata.insert(vdata.end(), data.begin(), data.end()); + + g->mOffset = index; + index += 4; + } + } + + mVBO = createBuffer(GL_ARRAY_BUFFER, vdata.size(), vdata.data(), GL_STATIC_DRAW); mIsFontLoaded = true; - mTTFfile = pFile; + CheckGL("End Font::loadFont"); } -void font_impl::loadSystemFont(const char* const pName, int pFontSize) +void font_impl::loadSystemFont(const char* const pName) { - //TODO do error checking once it is working std::string ttf_file_path; #ifndef OS_WIN // use fontconfig to get the file FcConfig* config = FcInitLoadConfigAndFonts(); if (!config) { - FT_THROW_ERROR("fontconfig initilization failed", fg::FG_ERR_FREETYPE_ERROR); + throw fg::Error("Fontconfig init failed", + __LINE__, __PRETTY_FUNCTION__, + fg::FG_ERR_FONTCONFIG_ERROR); } // configure the search pattern, FcPattern* pat = FcNameParse((const FcChar8*)(pName)); if (!pat) { - FT_THROW_ERROR("fontconfig pattern creation failed", fg::FG_ERR_FREETYPE_ERROR); + throw fg::Error("Fontconfig name parse failed", + __LINE__, __PRETTY_FUNCTION__, + fg::FG_ERR_FONTCONFIG_ERROR); } FcConfigSubstitute(config, pat, FcMatchPattern); @@ -274,15 +409,15 @@ void font_impl::loadSystemFont(const char* const pName, int pFontSize) ttf_file_path += matchedFontFiles[0]; #endif - loadFont(ttf_file_path.c_str(), pFontSize); + loadFont(ttf_file_path.c_str()); } void font_impl::render(int pWindowId, const float pPos[], const float pColor[], const char* pText, - int pFontSize, bool pIsVertical) + size_t pFontSize, bool pIsVertical) { + CheckGL("Begin font_impl::render "); if(!mIsFontLoaded) { - std::cerr<<"No font was loaded!, hence skipping text rendering."<atlasTextureId()); + glUniform1i(mTexIndex, 0); bindResources(pWindowId); - CheckGL("After Font::render bind resources"); - glActiveTexture(GL_TEXTURE0); - glUniform1i(tex_loc, 0); - glBindSampler(0, mSampler); - std::string str(pText); - for (std::string::iterator it = str.begin(); it != str.end(); ++it) { - char currChar = *it; + float loc_x = pPos[0]; + float loc_y = pPos[1]; - if(currChar == '\n') { - // if it is new line, move location to next line - loc_x = int(pPos[0]); - loc_y -= mNewLine * pFontSize / mLoadedPixelSize; - } else if (currChar <= '~' && currChar >= ' ') { - // regular characters are rendered as textured quad - int idx = int(currChar) - START_CHAR; - loc_x += mBearingX[idx] * pFontSize / mLoadedPixelSize; + if (pFontSizeMAX_FONT_SIZE) { + pFontSize = MAX_FONT_SIZE; + } - glBindTexture(GL_TEXTURE_2D, mCharTextures[idx]); + auto& glyphList = mGlyphLists[pFontSize - MIN_FONT_SIZE]; - /* rotate by 90 degress if we need - * to render the characters vertically */ - glm::mat4 modelView = glm::translate(glm::mat4(1.0f), - glm::vec3(float(loc_x), float(loc_y), 0.0f)); + for (size_t i=0; i=START_CHAR && ccode<=END_CHAR) { - // Draw letter - glDrawArrays(GL_TRIANGLE_STRIP, idx*4, 4); + int idx = ccode - START_CHAR; - loc_x += (mAdvX[idx] - mBearingX[idx]) * pFontSize / mLoadedPixelSize; - } - /* if the text needs to be rendered vertically, - * move the pen cursor to next line after each - * character render mandatorily */ - if (pIsVertical) { - loc_x = int(pPos[0]); - loc_y -= mNewLine * pFontSize / mLoadedPixelSize; + Glyph* g = glyphList[idx]; + + if (!pIsVertical) + loc_x += g->mBearingX; + + glm::mat4 modelView = glm::translate(glm::mat4(1.0f), glm::vec3(loc_x, loc_y, 0.0f)); + + glUniformMatrix4fv(mMMatIndex, 1, GL_FALSE, (GLfloat*)&modelView); + + glDrawArrays(GL_TRIANGLE_STRIP, g->mOffset, 4); + + if (pIsVertical) { + loc_y -= (pFontSize-g->mAdvanceY); + } else { + loc_x += g->mAdvanceX; + } } } @@ -357,7 +481,8 @@ void font_impl::render(int pWindowId, glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); - CheckGL("After Font::render "); + + CheckGL("End font_impl::render "); } } @@ -380,14 +505,14 @@ Font::~Font() delete value; } -void Font::loadFont(const char* const pFile, int pFontSize) +void Font::loadFontFile(const char* const pFile) { - value->loadFont(pFile, pFontSize); + value->loadFont(pFile); } -void Font::loadSystemFont(const char* const pName, int pFontSize) +void Font::loadSystemFont(const char* const pName) { - value->loadSystemFont(pName, pFontSize); + value->loadSystemFont(pName); } internal::_Font* Font::get() const diff --git a/src/font.hpp b/src/font.hpp index d0505ea7..cdf8232a 100644 --- a/src/font.hpp +++ b/src/font.hpp @@ -10,15 +10,20 @@ #pragma once #include +#include + +#include #include #include -#include -static const int NUM_CHARS = 95; +static const size_t MIN_FONT_SIZE = 8; +static const size_t MAX_FONT_SIZE = 36; namespace internal { +typedef std::vector GlyphList; + class font_impl { private: /* VAO map to store a vertex array object @@ -26,24 +31,25 @@ class font_impl { std::map mVAOMap; /* attributes */ - bool mIsFontLoaded; std::string mTTFfile; - std::vector mVertexData; - int mWidth; - int mHeight; - GLuint mVBO; - GLuint mProgram; - GLuint mSampler; - - GLuint mCharTextures[NUM_CHARS]; - int mAdvX[NUM_CHARS], mAdvY[NUM_CHARS]; - int mBearingX[NUM_CHARS], mBearingY[NUM_CHARS]; - int mCharWidth[NUM_CHARS], mCharHeight[NUM_CHARS]; - int mLoadedPixelSize, mNewLine; - - /* helper function to extract glyph of - * ASCII character pointed by pIndex*/ - void extractGlyph(int pIndex); + bool mIsFontLoaded; + FontAtlas* mAtlas; + GLuint mVBO; + GLuint mProgram; + int mOrthoW; + int mOrthoH; + + std::vector mGlyphLists; + + /* OpenGL Data */ + glm::mat4 mProjMat; + GLuint mPMatIndex; + GLuint mMMatIndex; + GLuint mTexIndex; + GLuint mClrIndex; + + /* load all glyphs and create character atlas */ + void loadAtlasWithGlyphs(const size_t pFontSize); /* helper functions to bind and unbind * rendering resources */ @@ -59,12 +65,12 @@ class font_impl { ~font_impl(); void setOthro2D(int pWidth, int pHeight); - void loadFont(const char* const pFile, int pFontSize); - void loadSystemFont(const char* const pName, int pFontSize); + void loadFont(const char* const pFile); + void loadSystemFont(const char* const pName); void render(int pWindowId, const float pPos[2], const float pColor[4], const char* pText, - int pFontSize = -1, bool pIsVertical = false); + size_t pFontSize, bool pIsVertical = false); }; class _Font { @@ -82,12 +88,12 @@ class _Font { fnt->setOthro2D(pWidth, pHeight); } - inline void loadFont(const char* const pFile, int pFontSize) { - fnt->loadFont(pFile, pFontSize); + inline void loadFont(const char* const pFile) { + fnt->loadFont(pFile); } - inline void loadSystemFont(const char* const pName, int pFontSize) { - fnt->loadSystemFont(pName, pFontSize); + inline void loadSystemFont(const char* const pName) { + fnt->loadSystemFont(pName); } }; diff --git a/src/font_atlas.cpp b/src/font_atlas.cpp new file mode 100644 index 00000000..6bb6a810 --- /dev/null +++ b/src/font_atlas.cpp @@ -0,0 +1,239 @@ +/******************************************************* +* Copyright (c) 2015-2019, ArrayFire +* All rights reserved. +* +* This file is distributed under 3-clause BSD license. +* The complete license agreement can be obtained at: +* http://arrayfire.com/licenses/BSD-3-Clause +********************************************************/ + +#include +#include + +#include +#include + +namespace internal +{ + +int FontAtlas::fit(const size_t pIndex, const size_t pWidth, const size_t pHeight) +{ + auto node = nodes[pIndex]; + int x = node.x; + int y = node.y; + int widthLeft = pWidth; + int i = pIndex; + + if ((x + pWidth) > (mWidth-1)) { + return -1; + } + + y = node.y; + + while (widthLeft > 0) { + auto node = nodes[i]; + if (node.y > y) { + y = node.y; + } + if ((y + pHeight) > (mHeight-1)) { + return -1; + } + widthLeft -= node.z; + ++i; + } + return y; +} + +void FontAtlas::merge() +{ + for (size_t i=0; i< nodes.size()-1; ++i) { + glm::vec3& node = nodes[i]; + auto next = nodes[i+1]; + + if (node.y == next.y) { + node.z += next.z; + nodes.erase(nodes.begin()+(i+1)); + --i; + } + } +} + +FontAtlas::FontAtlas(const size_t pWidth, const size_t pHeight, const size_t pDepth) + : mWidth(pWidth), mHeight(pHeight), mDepth(pDepth), mUsed(0), mId(0) +{ + CheckGL("Begin FontAtlas::FontAtlas"); + if (!((pDepth == 1) || (pDepth == 3) || (pDepth == 4))) { + throw fg::Error("Font Atlas", __LINE__, "Invalid depth argument", fg::FG_ERR_INTERNAL); + } + + glGenTextures(1, &mId); + glBindTexture(GL_TEXTURE_2D, mId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + + // one pixel border around the whole atlas to + // avoid any artefact when sampling texture + nodes.push_back(glm::vec3(1,1,mWidth-2)); + + mData.reserve(mWidth*mHeight*mDepth); + CheckGL("End FontAtlas::FontAtlas"); +} + +FontAtlas::~FontAtlas() +{ + nodes.clear(); + mData.clear(); + if (mId) { + glDeleteTextures(1, &mId); + } +} + +size_t FontAtlas::width() const +{ + return mWidth; +} + +size_t FontAtlas::height() const +{ + return mHeight; +} + +size_t FontAtlas::depth() const +{ + return mDepth; +} + +glm::vec4 FontAtlas::getRegion(const size_t pWidth, const size_t pHeight) +{ + glm::vec4 region(0, 0, pWidth, pHeight); + + size_t best_height = INT_MAX; + int best_index = -1; + size_t best_width = INT_MAX; + int y; + + for(size_t i=0; i= 0) { + auto node = nodes[i]; + if ( ((y + pHeight) < best_height) || + (((y + pHeight) == best_height) && (node.z < best_width)) ) + { + best_height = y + pHeight; + best_index = i; + best_width = node.z; + region.x = node.x; + region.y = y; + } + } + } + + if (best_index == -1) { + region.x = -1; + region.y = -1; + region.z = 0; + region.w = 0; + return region; + } + + glm::vec3 node(region.x, region.y+pHeight, pWidth); + + nodes.insert(nodes.begin()+best_index, node); + + for(size_t i = best_index+1; i < nodes.size(); ++i) + { + glm::vec3& node = nodes[i]; + auto prev = nodes[i-1]; + + if (node.x < (prev.x + prev.z) ) + { + int shrink = prev.x + prev.z - node.x; + node.x += shrink; + node.z -= shrink; + if (node.z <= 0) { + nodes.erase(nodes.begin()+i); + --i; + } else { + break; + } + } else { + break; + } + } + + merge(); + mUsed += pWidth * pHeight; + + return region; +} + +bool FontAtlas::setRegion(const size_t pX, const size_t pY, + const size_t pWidth, const size_t pHeight, + const uchar* pData, const size_t pStride) +{ + if (pX>0 && pY>0 && pX < (mWidth-1) && (pX+pWidth) <= (mWidth-1) && + pY <(mHeight-1) && (pY+pHeight) <= (mHeight-1)) + { + size_t depth = mDepth; + size_t charsize = sizeof(uchar); + + for (size_t i=0; i + +#include + +#include + +namespace internal +{ + +class FontAtlas { + private: + size_t mWidth; + size_t mHeight; + size_t mDepth; + size_t mUsed; + GLuint mId; + + std::vector mData; + std::vector nodes; + + /* helper functions */ + int fit(const size_t pIndex, const size_t pWidth, const size_t pHeight); + void merge(); + + public: + FontAtlas(const size_t pWidth, const size_t pHeight, const size_t pDepth); + ~FontAtlas(); + + size_t width() const; + size_t height() const; + size_t depth() const; + + glm::vec4 getRegion(const size_t pWidth, const size_t pHeight); + bool setRegion(const size_t pX, const size_t pY, + const size_t pWidth, const size_t pHeight, + const uchar* pData, const size_t pStride); + + void upload(); + void clear(); + + GLuint atlasTextureId() const; +}; + +struct Glyph { + size_t mWidth; + size_t mHeight; + + size_t mBearingX; + size_t mBearingY; + + float mAdvanceX; + float mAdvanceY; + + /* normalized texture coordinate (x) of top-left corner */ + float mS0, mT0; + + /* First normalized texture coordinate (x) of bottom-right corner */ + float mS1, mT1; + + /* render quad vbo offset */ + size_t mOffset; +}; + +} diff --git a/src/shaders/font_fs.glsl b/src/shaders/font_fs.glsl index a0a33b53..87d8e8f9 100644 --- a/src/shaders/font_fs.glsl +++ b/src/shaders/font_fs.glsl @@ -8,7 +8,6 @@ out vec4 outputColor; void main() { - vec4 texC = texture(tex, texCoord); - vec4 alpha = vec4(1.0, 1.0, 1.0, texC.r); - outputColor = alpha*textColor; + vec4 texC = texture(tex, texCoord); + outputColor = vec4(textColor.rgb, textColor.a*texC.r); }