First stab at text rendering by rendering glyphs (using Qt's font rendering)

into textures, storing the associated metrics, and drawing strings as
sequences of textured quads.
This commit is contained in:
Andrzej Kapolka 2013-04-29 18:54:12 -07:00
parent 30414fb433
commit a8c28a6d2a
2 changed files with 210 additions and 0 deletions

View file

@ -0,0 +1,129 @@
//
// TextRenderer.cpp
// interface
//
// Created by Andrzej Kapolka on 4/24/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
#include <QFont>
#include <QPaintEngine>
#include <QtDebug>
#include "InterfaceConfig.h"
#include "TextRenderer.h"
// the width/height of the cached glyph textures
const int IMAGE_SIZE = 256;
Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) :
_textureID(textureID), _location(location), _bounds(bounds), _width(width) {
}
TextRenderer::TextRenderer(const char* family, int pointSize, int weight, bool italic)
: _font(family, pointSize, weight, italic),
_metrics(_font), _x(IMAGE_SIZE), _y(IMAGE_SIZE), _rowHeight(0) {
}
void TextRenderer::draw(int x, int y, const char* str) {
glEnable(GL_TEXTURE_2D);
for (const char* ch = str; *ch != 0; ch++) {
const Glyph& glyph = getGlyph(*ch);
if (glyph.textureID() == 0) {
x += glyph.width();
continue;
}
glBindTexture(GL_TEXTURE_2D, glyph.textureID());
int left = x + glyph.bounds().x();
int right = x + glyph.bounds().x() + glyph.bounds().width();
int bottom = y + glyph.bounds().y();
int top = y + glyph.bounds().y() + glyph.bounds().height();
float scale = 1.0 / IMAGE_SIZE;
float ls = glyph.location().x() * scale;
float rs = (glyph.location().x() + glyph.bounds().width()) * scale;
float bt = glyph.location().y() * scale;
float tt = (glyph.location().y() + glyph.bounds().height()) * scale;
glBegin(GL_QUADS);
glTexCoord2f(ls, bt);
glVertex2f(left, bottom);
glTexCoord2f(rs, bt);
glVertex2f(right, bottom);
glTexCoord2f(rs, tt);
glVertex2f(right, top);
glTexCoord2f(ls, tt);
glVertex2f(left, top);
glEnd();
x += glyph.width();
}
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
}
int TextRenderer::computeWidth(char ch)
{
return getGlyph(ch).width();
}
int TextRenderer::computeWidth(const char* str)
{
int width = 0;
for (const char* ch = str; *ch != 0; ch++) {
width += computeWidth(*ch);
}
return width;
}
const Glyph& TextRenderer::getGlyph(char c) {
Glyph& glyph = _glyphs[c];
if (glyph.isValid()) {
return glyph;
}
QRect bounds = _metrics.boundingRect(c);
if (bounds.isEmpty()) {
glyph = Glyph(0, QPoint(), QRect(), _metrics.width(c));
return glyph;
}
if (_x + bounds.width() > IMAGE_SIZE) {
// we can't fit it on the current row; move to next
_y += _rowHeight;
_x = _rowHeight = 0;
}
if (_y + bounds.height() > IMAGE_SIZE) {
// can't fit it on current texture; make a new one
glGenTextures(1, &_textureID);
_x = _y = _rowHeight = 0;
glBindTexture(GL_TEXTURE_2D, _textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, IMAGE_SIZE, IMAGE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
} else {
glBindTexture(GL_TEXTURE_2D, _textureID);
}
// render the glyph into an image and copy it into the texture
QImage image(bounds.width(), bounds.height(), QImage::Format_ARGB32);
image.fill(0);
{
QPainter painter(&image);
painter.setFont(_font);
painter.setPen(QColor(255, 255, 255));
painter.drawText(-bounds.x(), -bounds.y(), QChar(c));
}
glTexSubImage2D(GL_TEXTURE_2D, 0, _x, _y, bounds.width(), bounds.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.constBits());
glyph = Glyph(_textureID, QPoint(_x, _y), bounds, _metrics.width(c));
_x += bounds.width();
_rowHeight = qMax(_rowHeight, bounds.height());
glBindTexture(GL_TEXTURE_2D, 0);
return glyph;
}

View file

@ -0,0 +1,81 @@
//
// TextRenderer.h
// interface
//
// Created by Andrzej Kapolka on 4/26/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__TextRenderer__
#define __interface__TextRenderer__
#include <QFont>
#include <QFontMetrics>
#include <QHash>
#include <QImage>
class Glyph;
class TextRenderer {
public:
TextRenderer(const char* family, int pointSize = -1, int weight = -1, bool italic = false);
const QFontMetrics& metrics() const { return _metrics; }
void draw(int x, int y, const char* str);
int computeWidth(char ch);
int computeWidth(const char* str);
private:
const Glyph& getGlyph (char c);
// the font to render
QFont _font;
// the font metrics
QFontMetrics _metrics;
// maps characters to cached glyph info
QHash<char, Glyph> _glyphs;
// the id of the glyph texture to which we're currently writing
GLuint _textureID;
// the position within the current glyph texture
int _x, _y;
// the height of the current row of characters
int _rowHeight;
};
class Glyph {
public:
Glyph(int textureID = 0, const QPoint& location = QPoint(), const QRect& bounds = QRect(), int width = 0);
GLuint textureID() const { return _textureID; }
const QPoint& location () const { return _location; }
const QRect& bounds() const { return _bounds; }
int width () const { return _width; }
bool isValid() { return _width != 0; }
private:
// the id of the OpenGL texture containing the glyph
GLuint _textureID;
// the location of the character within the texture
QPoint _location;
// the bounds of the character
QRect _bounds;
// the width of the character (distance to next, as opposed to bounds width)
int _width;
};
#endif /* defined(__interface__TextRenderer__) */