diff options
Diffstat (limited to 'src/libjin/Graphics/Font.cpp')
-rw-r--r-- | src/libjin/Graphics/Font.cpp | 349 |
1 files changed, 297 insertions, 52 deletions
diff --git a/src/libjin/Graphics/Font.cpp b/src/libjin/Graphics/Font.cpp index bb767db..a3a46dd 100644 --- a/src/libjin/Graphics/Font.cpp +++ b/src/libjin/Graphics/Font.cpp @@ -1,24 +1,28 @@ -#include "../modules.h" +#include "../jin_configuration.h" #if LIBJIN_MODULES_RENDER +#include "OpenGL.h" #include "font.h" #include <stdio.h> -#define STB_TRUETYPE_IMPLEMENTATION -#include "../3rdparty/stb/stb_truetype.h" #include "color.h" +#include "Shader.h" +#include "../Common/Array.hpp" namespace jin { namespace graphics { + #include "Shaders/font.shader.h" + using namespace std; using namespace jin::math; - + const int Font::TEXTURE_WIDTHS[] = { 128, 256, 256, 512, 512, 1024, 1024 }; const int Font::TEXTURE_HEIGHTS[] = { 128, 128, 256, 256, 512, 512, 1024 }; - static const char *ttf_utf8toCodepoint(const char *p, unsigned *res) { + /* utf8 byte string to unicode codepoint */ + static const char* utf8toCodepoint(const char *p, unsigned *res) { unsigned x, mask, shift; switch (*p & 0xf0) { case 0xf0: mask = 0x07; shift = 18; break; @@ -31,7 +35,6 @@ namespace graphics } x = (*p & mask) << shift; do { - /* Return early if we reach an unexpected NULL */ if (*(++p) == '\0') { *res = x; return p; @@ -43,80 +46,322 @@ namespace graphics return p + 1; } - /*static*/ Font* Font::createFont(const char* font, size_t size) + /* little endian unicode */ + static const char* unicodeLittleEndian(const char* p, unsigned* res) { } - /*static*/ Font* Font::createFont(const char* file) + /*static*/ Font* Font::createFont(FontData* fontData, unsigned int fontSzie) { - + Font* font; + try + { + font = new Font(fontData, fontSzie); + } + catch (...) + { + return nullptr; + } + return font; } - Font::Font() - : textureLevel(TEXTURE_SIZE_LEVEL_MAX) + void Font::destroyFont(Font* font) + { + if (font != nullptr) + delete font; + } + + Font::Font(FontData* f, unsigned int fontSize) + : cursor(0, 0) + , font(f) + , fontsize(fontSize) { - + font->pushFontsize(fontsize); + font->getVMetrics(&baseline, &descent); + estimateSize(); + font->popFontsize(); + /* create a default texture */ + createAtlas(); } - bool Font::createTexture() + /* estimate the size of atlas texture */ + void Font::estimateSize() { - GLuint t; - glGenTextures(1, &t); - glBindTexture(GL_TEXTURE_2D, t); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - /*Initialize the texture, attempting smaller sizes if initialization fails.*/ - bool initialized = false; - while (textureLevel >= 0) + for (int level = 0; level <= TEXTURE_SIZE_LEVEL_MAX; ++level) { - /*clear errors before initializing*/ - while (glGetError() != GL_NO_ERROR); - textureWidth = TEXTURE_WIDTHS[textureLevel]; - textureHeight = TEXTURE_HEIGHTS[textureLevel]; - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, textureWidth, textureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - initialized = (glGetError() == GL_NO_ERROR); - if (initialized || textureLevel <= 0) + if (descent * (descent*0.8) * 96 <= TEXTURE_WIDTHS[level] * TEXTURE_HEIGHTS[level]) + { + textureWidth = TEXTURE_WIDTHS[level]; + textureHeight = TEXTURE_HEIGHTS[level]; break; - --textureLevel; + } } - if (!initialized) + } + + Font::~Font() + { + map<unsigned int, Glyph*>::iterator it = glyphs.begin(); + for (; it != glyphs.end(); ++it) + { + delete it->second; + } + } + + GLuint Font::createAtlas() + { + GLuint t; + gl.flushError(); + t = gl.genTexture(); + gl.bindTexture(t); + gl.setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl.setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl.setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl.setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + gl.texImage(GL_RGBA8, textureWidth, textureHeight, GL_RGBA, GL_UNSIGNED_BYTE); + if (glGetError() != GL_NO_ERROR) { glDeleteTextures(1, &t); - glBindTexture(GL_TEXTURE_2D, 0); - return false; + gl.bindTexture(0); + return 0; } - textures.push_back(t); - glBindTexture(GL_TEXTURE_2D, 0); - return true; + atlases.push_back(t); + gl.bindTexture(0); + return t; + } + + void Font::print(const char* t, int x, int y, int lineheight, int spacing) + { + Page* page = typeset(t, lineheight, spacing); + print(page, x, y); + delete page; } - void Font::print(const char* text, int x, int y) + Page* Font::typeset(const char* text, int lineheight, int spacing) { - int len = strlen(text); - /* xy and uv list */ - vector<GlyphVertex> glyphvertices(len*4); - /* texture binded along with glyphvertices */ - vector<GlyphArrayDrawInfo> glyphinfolist; - float dx = 0; - float dy = 0; - //float lineheihgt = ; + // typesetting, for reducing draw call + const char* t = text; + Page* page = new Page(); + vector<GlyphArrayDrawInfo>& glyphinfolist = page->glyphinfolist; + vector<GlyphVertex>& glyphvertices = page->glyphvertices; + Vector2<int> p(0, 0); + Codepoint c; + int texture = -1; + Glyph* glyph = nullptr; + GlyphVertex vertex; + for (int i = 0; *t != NULL; i += 4) + { + t = utf8toCodepoint(t, &c); + if (c == 0x0D) + { + i -= 4; + continue; + } + /* new line */ + if (c == 0x0A) + { + p.y += lineheight; + p.x = 0; + i -= 4; + continue; + } + glyph = findGlyph(c); + if (texture != glyph->atlas) + { + GlyphArrayDrawInfo info; + info.start = i; + info.count = 0; + info.texture = glyph->atlas; + texture = glyph->atlas; + glyphinfolist.push_back(info); + } + glyphinfolist[glyphinfolist.size() - 1].count += 4; + Glyph::Bbox& bbox = glyph->bbox; + // 1 + vertex.x = p.x; vertex.y = p.y; + vertex.u = bbox.x; vertex.v = bbox.y; + glyphvertices.push_back(vertex); + // 2 + vertex.x = p.x; vertex.y = p.y + glyph->height; + vertex.u = bbox.x; vertex.v = bbox.y + bbox.height; + glyphvertices.push_back(vertex); + // 3 + vertex.x = p.x + glyph->width; vertex.y = p.y + glyph->height; + vertex.u = bbox.x + bbox.width; vertex.v = bbox.y + bbox.height; + glyphvertices.push_back(vertex); + // 4 + vertex.x = p.x + glyph->width; vertex.y = p.y; + vertex.u = bbox.x + bbox.width; vertex.v = bbox.y; + glyphvertices.push_back(vertex); + + p.x += glyph->width + spacing; + } + getTextBox(text, &page->width, &page->height, lineheight, spacing); + return page; } - /** - * unicodeȾļtextureϣglyphs - */ - Glyph* Font::addGlyph(unsigned int character) + void Font::print(const Page* page, int x, int y) { - GLuint texture = textures.back(); + Shader* shader = Shader::getCurrentJSL(); + const vector<GlyphArrayDrawInfo>& glyphinfolist = page->glyphinfolist; + const vector<GlyphVertex>& glyphvertices = page->glyphvertices; + gl.ModelMatrix.setTransformation(x, y, 0, 1, 1, 0, 0); + shader->sendMatrix4(Shader::MODEL_MATRIX, &gl.ModelMatrix); + shader->sendMatrix4(Shader::PROJECTION_MATRIX, &gl.ProjectionMatrix); + for (int i = 0; i < glyphinfolist.size(); ++i) + { + const GlyphArrayDrawInfo& info = glyphinfolist[i]; + shader->bindVertexPointer(2, GL_INT, sizeof(GlyphVertex), &glyphvertices[info.start].x); + shader->bindUVPointer(2, GL_FLOAT, sizeof(GlyphVertex), &glyphvertices[info.start].u); + gl.bindTexture(info.texture); + gl.drawArrays(GL_QUADS, 0, info.count); + gl.bindTexture(0); + } + } + int Font::getCharWidth(int c) + { + int adw, lsb; + font->pushFontsize(fontsize); + font->getHMetrics(c, &adw, &lsb); + font->popFontsize(); + return adw; } - Glyph* Font::findGlyph(unsigned int character) + int Font::getCharHeight(int c) + { + return descent; + } + + int Font::getTextWidth(const char* t, int spacing) + { + font->pushFontsize(fontsize); + int res = 0; + int tmp = 0; + const char *p = t; + while (*p) { + unsigned c; + p = utf8toCodepoint(p, &c); + if (*p == 0x0D) + continue; + if (*p == 0x0A) + { + tmp = 0; + continue; + } + tmp += getCharWidth(c) + spacing; + if (tmp > res) res = tmp; + } + font->popFontsize(); + return res; + } + + int Font::getTextHeight(const char* t, int lineheight) + { + font->pushFontsize(fontsize); + int res = 0; + bool newline = true; + while (*t) + { + unsigned c; + t = utf8toCodepoint(t, &c); + if (*t == 0x0A) + newline = true; + else if (*t == 0x0D); + else if (newline) + { + newline = false; + res += lineheight; + } + } + font->popFontsize(); + return res; + } + + void Font::getTextBox(const char* text, int* w, int* h, int lineheight, int spacing) + { + font->pushFontsize(fontsize); + *w = 0; + *h = 0; + int tmp = 0; + const char* p = text; + const char* pt = nullptr; + bool nl = true; + while (*p) { + unsigned c; + pt = p; + p = utf8toCodepoint(p, &c); + if (*pt == 0x0D) + continue; + if (*pt == 0x0A) + { + tmp = 0; + nl = true; + continue; + } + else if(nl) + { + nl = false; + *h += lineheight; + } + tmp += getCharWidth(c) + spacing; + if (tmp > *w) *w = tmp; + } + font->popFontsize(); + } + + Glyph* Font::bakeGlyph(unsigned int character) { + Glyph* glyph = (Glyph*)malloc(sizeof(Glyph)); + int w, h, xoff, yoff; + font->pushFontsize(fontsize); + GLuint atlas = atlases.back(); + const Color* bitmap = font->getCodepointBitmap(character, &w, &h, &xoff, &yoff); + int adw, lsb; + { + font->getHMetrics(character, &adw, &lsb); + font->popFontsize(); + if (cursor.x + adw > textureWidth ) + { + cursor.x = 0; + cursor.y += descent; + if (cursor.y + descent * 2 > textureHeight) + { + /* create new atlas */ + atlas = createAtlas(); + cursor.y = 0; + } + } + gl.bindTexture(atlas); + gl.texSubImage(cursor.x + xoff, cursor.y + yoff + baseline, w, h, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); + gl.bindTexture(); + delete[] bitmap; + } + glyph->atlas = atlas; + glyph->bbox.x = cursor.x / (float)textureWidth; + glyph->bbox.y = cursor.y / (float)textureHeight; + glyph->bbox.width = adw / (float)textureWidth; + glyph->bbox.height = descent / (float)textureHeight; + glyph->width = adw; + glyph->height = descent; + glyphs.insert(std::pair<unsigned int, Glyph*>(character, glyph)); + cursor.x += adw; + return glyph; + } + + Glyph* Font::findGlyph(unsigned int character) + { + map<unsigned int, Glyph*>::iterator it = glyphs.find(character); + if (it != glyphs.end()) + { + return it->second; + } + else + { + Glyph* glyph = bakeGlyph(character); + return glyph; + } } } // graphics |