Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First commit

  • Loading branch information...
commit be63dc3ddba2e760d30be11e6553158de8f7d303 0 parents
@Queatz authored
11 CMakeLists.txt
@@ -0,0 +1,11 @@
+cmake_minimum_required(VERSION 2.8)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
+
+project(Shikoba)
+include_directories(/usr/include/freetype2/)
+add_library(Shikoba SHARED Shikoba.cpp)
+target_link_libraries(Shikoba freetype GL)
+
+install(TARGETS Shikoba LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
+install_files(/include Shikoba.hpp)
15 Face.cpp
@@ -0,0 +1,15 @@
+Face::Face(Library * lib, const char * filepath, unsigned int index) {
+ _library = lib;
+
+ FT_Error err;
+ err = FT_New_Face(_library->_ft_library, filepath, index, &_ft_face);
+
+ if(err) {
+ printf("Could not load (sad) face. :(\n");
+ return;
+ }
+}
+
+Face::~Face() {
+ FT_Done_Face(_ft_face);
+}
217 Font.cpp
@@ -0,0 +1,217 @@
+Font::Font(Face *face, float width, float height, int horizontal_dpi, int vertical_dpi) {
+ _face = face;
+
+ FT_Error err;
+ err = FT_New_Size(_face->_ft_face, &_ft_size);
+
+ if(err) {
+ printf("Invalid font!\n");
+ return;
+ }
+
+ FT_Activate_Size(_ft_size);
+
+ err = FT_Set_Char_Size(_face->_ft_face, width * 64.0, height * 64.0, horizontal_dpi, vertical_dpi);
+
+ if(err) {
+ printf("Invalid font size!\n");
+ return;
+ }
+
+ GLuint texID;
+
+ GLint last;
+ glGetIntegerv(GL_TEXTURE_BINDING_2D, &last);
+
+ // Start with one texture; more will be added if space runs out
+ glGenTextures(1, &texID);
+ glBindTexture(GL_TEXTURE_2D, texID);
+
+ if(!_face->_ft_face->size) {
+ printf("Invalid font size the second generation!\n");
+ return;
+ }
+
+ // Guess at how much space we'll need; expands when needed
+ _texture_width = _ft_size->metrics.x_ppem * 16;
+ _texture_height = _ft_size->metrics.y_ppem * 16;
+
+ if(_texture_width == 0 || _texture_height == 0) {
+ printf("Invalid font size the third generation!\n");
+ return;
+ }
+
+ if(_texture_width > _face->_library->_context.maximum_texture_size / 4)
+ _texture_width = _face->_library->_context.maximum_texture_size / 4;
+
+ if(_texture_height > _face->_library->_context.maximum_texture_size / 4)
+ _texture_height = _face->_library->_context.maximum_texture_size / 4;
+
+ // Causes segfaults with large sizes
+ GLubyte * data;
+ int dsize = _texture_width * _texture_height;
+ data = new GLubyte[dsize];
+
+ int I;
+ for(I = 0; I < dsize; I++)
+ data[I] = (GLubyte)0;
+
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, _texture_width, _texture_height, 0, GL_RED, GL_UNSIGNED_BYTE, (const GLvoid *)data);
+ glGenerateMipmap(GL_TEXTURE_2D);
+
+ delete [] data;
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glBindTexture(GL_TEXTURE_2D, last);
+
+ // All glyphs have a 1 pixel padding
+ _texturepen_x = 1;
+ _texturepen_y = 1;
+
+ // Tallest glyph in the current row
+ _texturerow_h = 0;
+
+ _textures.push_back(texID);
+}
+
+Font::~Font() {
+ if(!_textures.empty())
+ glDeleteTextures(_textures.size(), &_textures[0]);
+
+ FT_Done_Size(_ft_size);
+}
+
+float Font::height() {
+ return (float)(_ft_size->metrics.height >> 6);
+}
+
+float Font::ascender() {
+ return (float)(_ft_size->metrics.ascender >> 6);
+}
+
+float Font::descender() {
+ return (float)(_ft_size->metrics.descender >> 6);
+}
+
+float Font::advance(const unsigned char * utf8) {
+ FT_Error err;
+ err = FT_Activate_Size(_ft_size);
+
+ const unsigned char * i = utf8;
+ uint32_t c;
+
+ const Glyph * temp;
+ const Glyph * lasttemp = 0;
+
+ GLfloat pen_x = 0.0;
+
+ FT_Vector kern;
+ int has_kerning = FT_HAS_KERNING(_face->_ft_face);
+
+ while(*i != 0) {
+ c = utf8::unchecked::next(i);
+ temp = glyph(c);
+
+ if(has_kerning && lasttemp != NULL) {
+ err = FT_Get_Kerning(_face->_ft_face, lasttemp->_glyphid, temp->_glyphid, FT_KERNING_DEFAULT, &kern);
+ if(err == 0)
+ pen_x += (float)(kern.x >> 6);
+ }
+
+ pen_x += (float)(temp->_ft_advance.x >> 6);
+
+ lasttemp = temp;
+ }
+
+ return pen_x;
+}
+
+const Glyph * Font::glyph(const uint32_t & c) {
+ if(_glyphs.count(c) == 0) {
+ FT_Error err;
+
+ err = FT_Load_Char(_face->_ft_face, (const FT_UInt)c, FT_LOAD_DEFAULT | FT_LOAD_IGNORE_TRANSFORM | FT_LOAD_RENDER);
+
+ if(err) {
+ printf("Char load fail!\n");
+ return NULL;
+ }
+
+ // Insert into texture
+
+ if(_textures.empty()) {
+ printf("No texture!\n");
+ return NULL;
+ }
+
+ GLint last;
+ glGetIntegerv(GL_TEXTURE_BINDING_2D, &last);
+
+ if((GLuint)last != _textures.back())
+ glBindTexture(GL_TEXTURE_2D, _textures.back());
+
+ // Shortcut
+ FT_Bitmap * bitmap = &_face->_ft_face->glyph->bitmap;
+
+ if(_texturepen_x + bitmap->width > _texture_width) {
+ _texturepen_y += _texturerow_h + 1;
+ _texturepen_x = 1;
+
+ if(_texturepen_y + bitmap->rows > _texture_height - 1) {
+ printf("Texture overflow...\n");
+ }
+ }
+
+ GLubyte * data;
+ int dsize = bitmap->width * bitmap->rows;
+ data = new GLubyte[dsize];
+
+ int x, y, i = 0;
+ for(y = 0; y < bitmap->rows; y++)
+ for(x = 0; x < bitmap->width; x++)
+ data[x + (bitmap->rows - 1 - y) * bitmap->width] = bitmap->buffer[i++];
+
+ GLint uplast;
+ glGetIntegerv(GL_UNPACK_ALIGNMENT, &uplast);
+ if(uplast != 1)
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+ glTexSubImage2D(GL_TEXTURE_2D, 0, _texturepen_x, _texturepen_y, bitmap->width, bitmap->rows, GL_RED, GL_UNSIGNED_BYTE, data);
+
+ delete [] data;
+
+ if(uplast != 1)
+ glPixelStorei(GL_UNPACK_ALIGNMENT, uplast);
+
+ if((GLuint)last != _textures.back())
+ glBindTexture(GL_TEXTURE_2D, last);
+
+ _glyphs[c].tex = 0;
+
+ _glyphs[c]._glyphid = FT_Get_Char_Index(_face->_ft_face, (const FT_ULong)c);
+
+ _glyphs[c]._ft_metrics = _face->_ft_face->glyph->metrics;
+ _glyphs[c]._ft_advance = _face->_ft_face->glyph->advance;
+
+ _glyphs[c].vertices[0] = (float)_face->_ft_face->glyph->bitmap_left - .5;
+ _glyphs[c].vertices[1] = -((float)bitmap->rows - (float)_face->_ft_face->glyph->bitmap_top) - .5;
+ _glyphs[c].vertices[2] = (float)bitmap->width + (float)_face->_ft_face->glyph->bitmap_left + .5;
+ _glyphs[c].vertices[3] = -((float)bitmap->rows - (float)_face->_ft_face->glyph->bitmap_top) + (float)bitmap->rows + .5;
+
+ _glyphs[c].texcoords[0] = (((float)_texturepen_x) - .5) / (float)_texture_width;
+ _glyphs[c].texcoords[1] = (((float)_texturepen_y) - .5) / (float)_texture_height;
+ _glyphs[c].texcoords[2] = ((float)(_texturepen_x + bitmap->width) + .5) / (float)_texture_width;
+ _glyphs[c].texcoords[3] = ((float)(_texturepen_y + bitmap->rows) + .5) / (float)_texture_height;
+
+ // Ready for next character when needed
+
+ _texturepen_x += bitmap->width + 1;
+
+ if((unsigned int)bitmap->rows > _texturerow_h)
+ _texturerow_h = bitmap->rows;
+ }
+
+ return &_glyphs[c];
+}
26 Library.cpp
@@ -0,0 +1,26 @@
+Library::Library() {
+ FT_Error err;
+ err = FT_Init_FreeType(&_ft_library);
+
+ if(err) {
+ printf("Could not initialize library.\n");
+ }
+
+ GLint i;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &i);
+ _context.maximum_texture_size = (GLuint)i;
+
+ if(_context.maximum_texture_size == 0)
+ printf("Invalid context! Be wary!");
+}
+
+Library::~Library() {
+ if(_ft_library) {
+ FT_Error err;
+ err = FT_Done_FreeType(_ft_library);
+
+ if(err) {
+ printf("Could not historate library.\n");
+ }
+ }
+}
1  README
@@ -0,0 +1 @@
+Shikoba is a text library for OpenGL 3 and above using the FreeType 2 library.
12 Shikoba.cpp
@@ -0,0 +1,12 @@
+#include "Shikoba.hpp"
+
+#include <cmath>
+
+namespace Shikoba {
+
+#include "Library.cpp"
+#include "Face.cpp"
+#include "Font.cpp"
+#include "Text.cpp"
+
+}
189 Shikoba.hpp
@@ -0,0 +1,189 @@
+#ifdef _WIN32
+typedef unsigned int uint32_t;
+#include <GL/glew.h>
+#else
+#ifndef GL3_PROTOTYPES
+#define GL3_PROTOTYPES
+#endif
+#include <GL3/gl3.h>
+#endif
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_SIZES_H
+
+#include <utf8.h>
+#include <vector>
+#include <map>
+
+namespace Shikoba {
+
+struct Library;
+struct Face;
+struct Font;
+struct Text;
+
+/***
+Alignment constants
+ LEFT
+ RIGHT
+ CENTER
+ JUSTIFY
+
+JUSTIFY can be |'d with the others: CENTER | JUSTIFY
+***/
+const int LEFT = 1 << 0;
+const int RIGHT = 1 << 1;
+const int CENTER = 1 << 2;
+const int JUSTIFY = 1 << 3;
+
+struct Glyph {
+ FT_Glyph_Metrics _ft_metrics;
+ FT_Vector _ft_advance;
+ GLfloat vertices[4];
+ GLfloat texcoords[4];
+ GLuint tex;
+ FT_UInt _glyphid;
+};
+
+struct Text {
+ /***
+ Create a string using a font.
+ Font * | The font to use.
+ const unsigned char * | A Unicode 8-bit string.
+ [float] | Character spacing.
+ [float] | Line spacing.
+ [int] | Alignment. Can be LEFT, RIGHT, or CENTER, and can be |'d with JUSTIFY.
+ [float] | Maximum line width. 0 means unmetered.
+ [int] | Number of spaces a tab represents.
+ ***/
+ Text(Font *, const unsigned char *, float = 1.0, float = 1.0, int = LEFT, float = 0.0, int = 4);
+ ~Text();
+
+ /***
+ Draw the string. It will bind the font's texture and set up the vertex and texcoord attributes.
+ GLuint | Vertex position attribute (vec2).
+ GLuint | Texture coordinate attribute (vec2).
+ ***/
+ void draw(GLuint, GLuint);
+
+ /***
+ Get the number of lines in the string.
+ ***/
+ int lines();
+
+ /***
+ Get the maximum line advance.
+ ***/
+ float advance();
+
+ private:
+
+ int _lines;
+ float _maxlineadvance;
+
+ Font * _font;
+
+ GLuint _buffers[2];
+ GLsizei _characters;
+};
+
+/* Font */
+
+struct Font {
+ /***
+ Create a font from a face with a given size.
+ Face * | The face to use.
+ float | The font size, or horizontal size.
+ [float] | Vertical font size.
+ [int] | DPI of surface.
+ [int] | Vertical DPI.
+ ***/
+ Font(Face *, float, float = 0.0, int = 0, int = 0);
+ ~Font();
+
+ /***
+ The height of a single line.
+ ***/
+ float height();
+ /***
+ How far the font extends above the baseline.
+ ***/
+ float ascender();
+ /***
+ How far the font descends below the baseline.
+ ***/
+ float descender();
+ /***
+ Calculate the advance of a string.
+ const unsigned char * | The string.
+ ***/
+ float advance(const unsigned char *);
+
+ /***
+ Access a character glyph.
+ const ucs4_t & | The character.
+ ***/
+ const Glyph * glyph(const uint32_t &);
+
+ private:
+
+ FT_Size _ft_size;
+ Face * _face;
+
+ std::vector<GLuint> _textures;
+
+ std::map<uint32_t, Glyph> _glyphs;
+
+ unsigned int _texturepen_x;
+ unsigned int _texturepen_y;
+ unsigned int _texturerow_h;
+ unsigned int _texture_width;
+ unsigned int _texture_height;
+
+ friend struct Text;
+};
+
+/* Face */
+
+struct Face {
+ /***
+ Create a face from a font file.
+ const char * | File.
+ [unsigned int] | Face in the file.
+ ***/
+ Face(Library *, const char *, unsigned int = 0);
+ ~Face();
+
+ private:
+
+ FT_Face _ft_face;
+ Library * _library;
+
+ friend struct Font;
+ friend struct Text;
+};
+
+/* Library */
+
+struct Context {
+ GLuint maximum_texture_size;
+};
+
+struct Library {
+ /***
+ Create a library.
+ ***/
+ Library();
+ ~Library();
+
+ private:
+
+ FT_Library _ft_library;
+ Context _context;
+
+ friend struct Face;
+ friend struct Font;
+};
+
+} // namespace Shikoba
254 Text.cpp
@@ -0,0 +1,254 @@
+struct _lineglyph {
+ _lineglyph(const Glyph * g, int a, int b) : glyph(g), x(a), y(b) {}
+
+ const Glyph * glyph;
+ int x, y;
+};
+
+Text::Text(Font * font, const unsigned char * utf8, float spacing, float linespacing, int align, float maxwidth, int tabwidth) {
+ _font = font;
+
+ _maxlineadvance = 0.0;
+
+ FT_Error err;
+ err = FT_Activate_Size(_font->_ft_size);
+
+ if(err) {
+ printf("Bad size.\n");
+ return;
+ }
+
+ const unsigned char * i = utf8;
+ const unsigned char * i2;
+ const unsigned char * i3;
+ uint32_t c = 0;
+ uint32_t clast = 0;
+
+ const Glyph * temp;
+ const Glyph * lasttemp = 0;
+
+ std::vector<GLushort> indicedata;
+ std::vector<GLfloat> pendata;
+
+ GLfloat pen_x = 0.0;
+ GLfloat pen_y = 0.0;
+ GLfloat line_pen_x = 0.0;
+ GLfloat break_pen_x = 0.0;
+
+ GLsizei characters = 0;
+
+ FT_Vector kern;
+ int has_kerning = FT_HAS_KERNING(_font->_face->_ft_face);
+
+ int lines = 0;
+
+ err = FT_Load_Char(_font->_face->_ft_face, (const FT_UInt)' ', FT_LOAD_DEFAULT | FT_LOAD_IGNORE_TRANSFORM);
+
+ int spaceadvance = (_font->_face->_ft_face->glyph->advance.x >> 6);
+ tabwidth *= (float)spaceadvance;
+
+ std::vector<_lineglyph> lineglyphs;
+
+ bool linebroke = false;
+ int current_word_length;
+
+ while(1) {
+ if(*i == 0) break;
+ i2 = i;
+ clast = c;
+ c = utf8::unchecked::next(i);
+ if(*i == 0) goto doline;
+
+ temp = _font->glyph(c);
+
+ if(c == '\n' || c == '\r') {
+ if(clast == '\r' && c == '\n')
+ continue;
+
+ lasttemp = NULL;
+ current_word_length = 0;
+
+ linebroke = true;
+ goto doline;
+ }
+ else if(c == '\t') {
+ lasttemp = NULL;
+ current_word_length = 0;
+ if(clast != '\t' && clast != ' ')
+ break_pen_x = line_pen_x;
+ pen_x = (float)((int)((pen_x + tabwidth) / tabwidth)) * tabwidth * spacing;
+ i3 = i;
+ continue;
+ }
+ else if(c == ' ') {
+ lasttemp = NULL;
+ current_word_length = 0;
+ if(clast != '\t' && clast != ' ')
+ break_pen_x = line_pen_x;
+ pen_x += (float)spaceadvance * spacing;
+ i3 = i;
+ continue;
+ }
+
+ if(has_kerning && lasttemp != NULL) {
+ err = FT_Get_Kerning(_font->_face->_ft_face, lasttemp->_glyphid, temp->_glyphid, FT_KERNING_DEFAULT, &kern);
+ if(err == 0)
+ pen_x += (float)(kern.x >> 6);
+ }
+
+ if(maxwidth > 0.0 && !lineglyphs.empty() && pen_x + (float)(temp->_ft_advance.x >> 6) >= maxwidth) {
+ if(break_pen_x > 0.001) {
+ for(int ci = 0; ci < current_word_length; ci++)
+ lineglyphs.pop_back();
+
+ i = i3;
+ line_pen_x = break_pen_x;
+ } else {
+ i = i2;
+ }
+
+ goto doline;
+ }
+
+ lineglyphs.push_back(_lineglyph(temp, pen_x, pen_y));
+
+ line_pen_x = pen_x + (float)(temp->_ft_advance.x >> 6);
+ pen_x += (float)(temp->_ft_advance.x >> 6) * spacing;
+ current_word_length += 1;
+ lasttemp = temp;
+
+ continue;
+ doline:
+
+ float lineshift, linestretch;
+
+ if(maxwidth > 0.0 && !linebroke && align & JUSTIFY) {
+ linestretch = maxwidth / line_pen_x;
+ }
+ else {
+ linestretch = 1.0;
+ }
+
+ if(align & LEFT) {
+ lineshift = 0.0;
+ }
+ else if(align & RIGHT) {
+ lineshift = std::floor(line_pen_x * linestretch);
+ }
+ else if(align & CENTER) {
+ lineshift = std::floor(line_pen_x * linestretch / 2.0);
+ }
+
+ if(_maxlineadvance < line_pen_x * linestretch)
+ _maxlineadvance = line_pen_x * linestretch;
+
+ std::vector<_lineglyph>::iterator q;
+ for(q = lineglyphs.begin(); q != lineglyphs.end(); q++) {
+ pendata.push_back(std::floor((*q).x * linestretch) + (*q).glyph->vertices[0] - lineshift);
+ pendata.push_back((*q).y + (*q).glyph->vertices[1]);
+ pendata.push_back((*q).glyph->texcoords[0]);
+ pendata.push_back((*q).glyph->texcoords[1]);
+
+ pendata.push_back(std::floor((*q).x * linestretch) + (*q).glyph->vertices[0] - lineshift);
+ pendata.push_back((*q).y + (*q).glyph->vertices[3]);
+ pendata.push_back((*q).glyph->texcoords[0]);
+ pendata.push_back((*q).glyph->texcoords[3]);
+
+ pendata.push_back(std::floor((*q).x * linestretch) + (*q).glyph->vertices[2] - lineshift);
+ pendata.push_back((*q).y + (*q).glyph->vertices[3]);
+ pendata.push_back((*q).glyph->texcoords[2]);
+ pendata.push_back((*q).glyph->texcoords[3]);
+
+ pendata.push_back(std::floor((*q).x * linestretch) + (*q).glyph->vertices[2] - lineshift);
+ pendata.push_back((*q).y + (*q).glyph->vertices[1]);
+ pendata.push_back((*q).glyph->texcoords[2]);
+ pendata.push_back((*q).glyph->texcoords[1]);
+
+ indicedata.push_back(characters * 4 + 0);
+ indicedata.push_back(characters * 4 + 1);
+ indicedata.push_back(characters * 4 + 2);
+ indicedata.push_back(characters * 4 + 0);
+ indicedata.push_back(characters * 4 + 2);
+ indicedata.push_back(characters * 4 + 3);
+
+ characters++;
+ }
+
+ lines += 1;
+ pen_x = 0.0;
+ pen_y -= (float)(_font->_ft_size->metrics.height >> 6) * linespacing;
+ break_pen_x = pen_x;
+ current_word_length = 0;
+
+ linebroke = false;
+
+ lineglyphs.clear();
+ }
+
+ _lines = lines;
+ _characters = characters;
+ GLint last, last2;
+ glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last);
+ glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last2);
+
+ glGenBuffers(2, _buffers);
+ glBindBuffer(GL_ARRAY_BUFFER, _buffers[0]);
+ glBufferData(GL_ARRAY_BUFFER, _characters * 4 * (2 + 2) * sizeof(GLfloat), (const GLvoid *)&pendata[0], GL_STATIC_DRAW);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffers[1]);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, _characters * sizeof(GLushort) * 6, (const GLvoid *)&indicedata[0], GL_STATIC_DRAW);
+
+ glBindBuffer(GL_ARRAY_BUFFER, last);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last2);
+}
+
+Text::~Text() {
+ if(_buffers[0] && _buffers[1])
+ glDeleteBuffers(2, _buffers);
+}
+
+int Text::lines() {
+ return _lines;
+}
+
+float Text::advance() {
+ return _maxlineadvance;
+}
+
+void Text::draw(GLuint vertexattribute, GLuint texcoordattribute) {
+ // Texture
+
+ GLint lasti;
+ glGetIntegerv(GL_TEXTURE_BINDING_2D, &lasti);
+
+ glBindTexture(GL_TEXTURE_2D, _font->_textures.back());
+
+ // Buffers
+
+ GLint last, last2;
+ glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last);
+ glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last2);
+
+ // Pen
+ if((GLuint)last != _buffers[0])
+ glBindBuffer(GL_ARRAY_BUFFER, _buffers[0]);
+
+ // Vertex + Texcoord
+
+ glVertexAttribPointer(vertexattribute, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, (const GLvoid *)0);
+ glVertexAttribPointer(texcoordattribute, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, (const GLvoid *)(sizeof(GLfloat) * 2));
+
+ if((GLuint)last2 != _buffers[1])
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffers[1]);
+
+ // Draw
+ glDrawElements(GL_TRIANGLES, _characters * 6, GL_UNSIGNED_SHORT, (const GLvoid *)0);
+
+ // Restore
+ glBindBuffer(GL_ARRAY_BUFFER, last);
+
+ if((GLuint)last2 != _buffers[1])
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last2);
+
+ glBindTexture(GL_TEXTURE_2D, lasti);
+}
Please sign in to comment.
Something went wrong with that request. Please try again.