I have updated demo for TTFTriangulator (simple C++ library designed to load a truetype font and triangulate its glyphs in real time) library to something working (I think). It is a little chaotic, but also very simple and generic, so easy to reuse (btw: it uses Qt for I/O and windows creation, but you can also use the code I modified and that is using GFLW).
You can use my amalgamated (and dependency-free) version, available here:
-rw-r--r-- 1 piecuchp staff 69K Jul 26 17:48 TTF.cpp
-rwxr-xr-x 1 piecuchp staff 93K Jul 26 06:45 TTF.h
-rw-r--r-- 1 piecuchp staff 217K Jul 26 06:50 TTF.o
(it is also modified to use QFile for I/O so you can work with Qt's embedded resources)
#include <QDebug>
#include <QElapsedTimer>
#include <QMouseEvent>
#include <QPainter>
#include <QWindow>
#include <QOpenGLContext>
#include <QOpenGLShaderProgram>
#include <QOpenGLPaintDevice>
#include <QOpenGLFunctions>
#include <QApplication>
#include "TTF.h"
using namespace TTF;
const char *fragCodeSimple = " \n\
varying vec3 tpos; \n\
float round(float val) \n\
{ \n\
return sign(val)*floor(abs(val)+0.5); \n\
} \n\
void main() \n\
{ \n\
float alpha = round((tpos.x*tpos.x-tpos.y)*tpos.z+0.5); \n\
gl_FragColor = alpha *vec4(1.0,1.0,1.0,1.0); \n\
} \n\
";
const char *fragCode =" \n\
varying vec3 tpos; \n\
void main() \n\
{ \n\
float alpha = 1.0; \n\
if (tpos.z != 0.0) \n\
{ \n\
vec2 p = tpos.xy; \n\
// Gradients \n\
vec2 px = dFdx(p); \n\
vec2 py = dFdy(p); \n\
// Chain rule \n\
float fx = ((2.0*p.x)*px.x-px.y); \n\
float fy = ((2.0*p.x)*py.x-py.y); \n\
// Signed distance \n\
float dist = fx*fx + fy*fy; \n\
float sd = (p.x*p.x - p.y)*tpos.z/sqrt(dist); \n\
// Linear alpha \n\
alpha = 0.5 - sd; \n\
if (alpha < 0.0) // Outside \n\
discard; \n\
} \n\
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); \n\
} \n\
";
const char *vertCode = " \n\
attribute float t; \n\
attribute float c; \n\
attribute vec2 pos; \n\
varying vec3 tpos; \n\
void main(void) \n\
{ \n\
tpos = vec3(t*0.5, max(t-1.0, 0.0), c); \n\
gl_Position = gl_ModelViewProjectionMatrix*vec4(pos, 0.0, 1.0);\n\
} \n\
";
double qtGetTime() {
static QElapsedTimer timer;
if (!timer.isValid())
timer.start();
return timer.elapsed() / 1000.;
}
class OpenGLWindow : public QWindow, public QOpenGLFunctions
{
Q_OBJECT
typedef void (^RenderBlock)();
private:
bool m_done, m_update_pending, m_auto_refresh;
QOpenGLContext *m_context;
QOpenGLShaderProgram m_shader;
Font m_f;
public:
QPoint cursorPos;
public:
OpenGLWindow(QWindow *parent = 0) : QWindow(parent)
, m_update_pending(false)
, m_auto_refresh(true)
, m_context(0)
, m_f(":/fonts/VinMonoPro-Light.ttf")
, m_done(false) {
setSurfaceType(QWindow::OpenGLSurface);
}
~OpenGLWindow() { }
void setAutoRefresh(bool a) { m_auto_refresh = a; }
void initialize() {
qDebug() << "OpenGL infos with gl functions:";
qDebug() << "-------------------------------";
qDebug() << " Renderer:" << (const char*)glGetString(GL_RENDERER);
qDebug() << " Vendor:" << (const char*)glGetString(GL_VENDOR);
qDebug() << " OpenGL Version:" << (const char*)glGetString(GL_VERSION);
qDebug() << " GLSL Version:" << (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
m_shader.addShaderFromSourceCode(QOpenGLShader::Vertex, vertCode);
m_shader.addShaderFromSourceCode(QOpenGLShader::Fragment, fragCode);
m_shader.link();
}
void update() { renderLater(); }
void render() {
glViewport(0, 0, width()*devicePixelRatio(), height()*devicePixelRatio());
glClearColor(0.8, 0.8, 0.8, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1, 1, -1, 1, -10, 10);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
m_shader.bind();
float scale = 0.1 + 0.2*fabs(cos(qtGetTime()/2.0));
glScalef(scale, scale, 1);
glTranslatef(-3.6, 0, 0);
renderMsg(m_f, "KomSoft");
m_shader.release();
}
void mousePressEvent(QMouseEvent *event) {
cursorPos = QPoint(event->x(), event->y());
Qt::KeyboardModifiers modifiers = event->modifiers();
if (event->buttons() & Qt::LeftButton) { }
}
void mouseReleaseEvent(QMouseEvent *event) {
cursorPos = QPoint(event->x(), event->y());
Qt::KeyboardModifiers modifiers = event->modifiers();
if (event->button() == Qt::LeftButton) { }
}
void mouseMoveEvent(QMouseEvent *event) {
cursorPos = QPoint(event->x(), event->y());
}
void keyPressEvent(QKeyEvent* event) {
switch(event->key()) {
case Qt::Key_Escape: quit(); break;
default: event->ignore();
break;
}
}
void quit() { m_done = true; }
bool done() { return m_done; }
protected:
void closeEvent(QCloseEvent *event) { quit(); }
bool event(QEvent *event) {
switch (event->type()) {
case QEvent::UpdateRequest:
m_update_pending = false;
renderNow();
return true;
default:
return QWindow::event(event);
}
}
void exposeEvent(QExposeEvent *event) {
Q_UNUSED(event);
if (isExposed()) renderNow();
}
public slots:
void renderLater() {
if (!m_update_pending) {
m_update_pending = true;
QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
}
}
void renderNow() {
if (!isExposed()) return;
bool needsInitialize = false;
if (!m_context) {
m_context = new QOpenGLContext(this);
m_context->setFormat(requestedFormat());
m_context->create();
needsInitialize = true;
}
m_context->makeCurrent(this);
if (needsInitialize) {
initializeOpenGLFunctions();
initialize();
}
render();
m_context->swapBuffers(this);
if (m_auto_refresh) renderLater();
}
private:
void renderMsg(const Font &f, const char *msg);
};
void OpenGLWindow::renderMsg(const Font &f, const char *msg)
{
TTF::FontMetrics font_metrics = f.GetFontMetrics(); // will tell you about the font
// Triangulator2DI, Triangulator2DII, Triangulator2DLinearI, Triangulator2DLinearII
TTF::Triangulator2DI triangulator;
for (int i = 0; i < strlen(msg); i++)
{
CodePoint cp(msg[i]);
f.TriangulateGlyph(cp, triangulator);
if (i > 0)
{
TTFCore::vec2t kerning = f.GetKerning(CodePoint(msg[i-1]), cp);
glTranslatef(0.9*kerning.x*0.001, kerning.y*0.001, 0);
}
struct vertex_t
{
vec2f pos;
signed char texCoord; // 0 = (0,0), 1 = (0.5,0), 2 = (1,1)
signed char coef; // -1 = CW edge, 0 = inner segment, +1 = CCW segment
};
QVector<vertex_t> verts;
for (auto tri : triangulator) {
TTF::vec2t v0 = triangulator[tri.i0];
TTF::vec2t v1 = triangulator[tri.i1];
TTF::vec2t v2 = triangulator[tri.i2];
// store in a buffer, or do something with it from here... up to you really
verts.push_back((vertex_t){{0.001f*v0.x, 0.001f*v0.y}, 0, static_cast<signed char>(tri.coef)});
verts.push_back((vertex_t){{0.001f*v1.x, 0.001f*v1.y}, 1, static_cast<signed char>(tri.coef)});
verts.push_back((vertex_t){{0.001f*v2.x, 0.001f*v2.y}, 2, static_cast<signed char>(tri.coef)});
}
if (verts.size())
{
GLint loc = m_shader.attributeLocation("t");
glEnableVertexAttribArray(loc);
glVertexAttribPointer(loc, 1, GL_BYTE, GL_FALSE, sizeof(vertex_t), &verts[0].texCoord);
loc = m_shader.attributeLocation("c");
glEnableVertexAttribArray(loc);
glVertexAttribPointer(loc, 1, GL_BYTE, GL_FALSE, sizeof(vertex_t), &verts[0].coef);
loc = m_shader.attributeLocation("pos");
glEnableVertexAttribArray(loc);
glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), &verts[0].pos);
glDrawArrays(GL_TRIANGLES, 0, verts.size());
}
#if 0
printf("%c: %d verts\n", msg[i], verts.size());
for (int j = 0; j < verts.size(); j++)
{
const vertex_t &mv = verts[j];
printf("%c %d %d, %d: (%f, %f), %d, %d\n", msg[i], j, j/3, j%3, mv.pos.x, mv.pos.y, mv.texCoord, mv.coef);
}
#endif
}
}
int main(int argc, char *argv[])
{
QSurfaceFormat surface_format = QSurfaceFormat::defaultFormat();
surface_format.setAlphaBufferSize( 8 );
surface_format.setDepthBufferSize( 24 );
// surface_format.setRedBufferSize( 8 );
// surface_format.setBlueBufferSize( 8 );
// surface_format.setGreenBufferSize( 8 );
// surface_format.setOption( QSurfaceFormat::DebugContext );
// surface_format.setProfile( QSurfaceFormat::NoProfile );
// surface_format.setRenderableType( QSurfaceFormat::OpenGLES );
// surface_format.setSamples( 4 );
// surface_format.setStencilBufferSize( 8 );
// surface_format.setSwapBehavior( QSurfaceFormat::DefaultSwapBehavior );
// surface_format.setSwapInterval( 1 );
// surface_format.setVersion( 2, 0 );
QSurfaceFormat::setDefaultFormat( surface_format );
QApplication app(argc, argv);
OpenGLWindow w;
w.resize(800, 600);
w.show();
return app.exec();
}
#include "demo.moc"