TTFTriangulator demo

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"