488 lines
11 KiB
C++
488 lines
11 KiB
C++
#include "render/font.h"
|
|
#include "render/driver.h"
|
|
#include "render/vertexBuffer.h"
|
|
#include "main/initialization.h"
|
|
#include <stdio.h>
|
|
#include "str_util.h"
|
|
#include "vec2.h"
|
|
|
|
#ifdef _MSC_VER
|
|
#include <ft2build.h>
|
|
#else
|
|
#include <freetype2/ft2build.h>
|
|
#endif
|
|
|
|
#include FT_FREETYPE_H
|
|
#include FT_SIZES_H
|
|
|
|
#include <map>
|
|
#include <algorithm>
|
|
|
|
namespace resource {
|
|
extern render::Texture* queueImage(Image* img, int priority, bool mipmap, bool cachePixels);
|
|
};
|
|
|
|
namespace render {
|
|
|
|
Font* Font::createDummyFont() {
|
|
return new Font();
|
|
}
|
|
|
|
bool clipQuad(rectf pos, rectf source, const rectf* clip, vec2f* verts, vec2f* texcoords) {
|
|
if(clip) {
|
|
if(!clip->overlaps(pos))
|
|
return false;
|
|
|
|
if(!clip->isRectInside(pos)) {
|
|
//TODThere seem to be some oddities with the texture coordinates if a clip occurs
|
|
rectf clipped = clip->clipAgainst(pos);
|
|
source = source.clipProportional(pos, clipped);
|
|
pos = clipped;
|
|
}
|
|
}
|
|
|
|
verts[0] = vec2f(pos.topLeft.x, pos.topLeft.y);
|
|
verts[1] = vec2f(pos.botRight.x, pos.topLeft.y);
|
|
verts[3] = vec2f(pos.topLeft.x, pos.botRight.y);
|
|
verts[2] = vec2f(pos.botRight.x, pos.botRight.y);
|
|
|
|
texcoords[0] = vec2f(source.topLeft.x, source.topLeft.y);
|
|
texcoords[1] = vec2f(source.botRight.x, source.topLeft.y);
|
|
texcoords[3] = vec2f(source.topLeft.x, source.botRight.y);
|
|
texcoords[2] = vec2f(source.botRight.x, source.botRight.y);
|
|
|
|
return true;
|
|
}
|
|
|
|
class FontFT2 : public Font {
|
|
public:
|
|
std::vector<render::RenderState*> textures;
|
|
|
|
struct Glyph {
|
|
unsigned short x : 16, y : 16;
|
|
bool draw : 1;
|
|
unsigned char page : 7;
|
|
unsigned char w : 8, h : 8;
|
|
char wOff : 8, hOff : 8;
|
|
float xAdv;
|
|
int glyph_index;
|
|
};
|
|
|
|
struct GlyphEntry {
|
|
fontChar letter;
|
|
Glyph glyph;
|
|
|
|
bool operator<(const GlyphEntry& other) const { return letter < other.letter; }
|
|
GlyphEntry(fontChar Letter) : letter(Letter) {}
|
|
GlyphEntry(fontChar Letter, Glyph& Glyph) : letter(Letter), glyph(Glyph) {}
|
|
};
|
|
|
|
Glyph* lowChars;
|
|
FT_Face* face;
|
|
FT_Size size;
|
|
std::vector<GlyphEntry> hiChars;
|
|
|
|
double x_scale, y_scale;
|
|
unsigned glyphHeight, baseLine, textureHeight, textureWidth;
|
|
|
|
void draw(render::RenderDriver* driver, const char* text, int X, int Y, const Color* color, const recti* clip = 0) const {
|
|
float x = (float)X, y = (float)Y;
|
|
Color col = color ? *color : Color();
|
|
|
|
float xFactor = 1.f / float(textureWidth), yFactor = 1.f / float(textureHeight);
|
|
vec2f font_verts[4], font_tc[4];
|
|
|
|
rectf fclip;
|
|
if(clip)
|
|
fclip = rectf(*clip);
|
|
|
|
FT_Activate_Size(size);
|
|
|
|
unsigned lastTexture = (unsigned)textures.size();
|
|
VertexBufferTCV* buffer = 0;
|
|
|
|
u8it it(text);
|
|
fontChar lastC = 0;
|
|
|
|
while(fontChar c = it++) {
|
|
if(c == L'\n') {
|
|
y += glyphHeight;
|
|
x = (float)X;
|
|
lastC = 0;
|
|
}
|
|
else if(c == L'\t') {
|
|
x += 32.f - fmod(x - X, 32.f);
|
|
lastC = L' ';
|
|
}
|
|
else {
|
|
Glyph glyph;
|
|
|
|
if(c <= 0xff)
|
|
glyph = lowChars[c];
|
|
else {
|
|
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
|
if(g != hiChars.end() && g->letter == c) {
|
|
glyph = g->glyph;
|
|
}
|
|
else {
|
|
glyph = lowChars['?'];
|
|
}
|
|
}
|
|
|
|
//Don't kern anything involving digits
|
|
if(c >= '0' && c <= '9') {
|
|
lastC = 0;
|
|
}
|
|
else {
|
|
if(lastC) {
|
|
FT_Vector kerning;
|
|
FT_Get_Kerning(*face, lastC, glyph.glyph_index, 0/*FT_KERNING_UNFITTED*/, &kerning);
|
|
|
|
if(kerning.x != 0)
|
|
x += ((float)kerning.x) / 64.f;
|
|
}
|
|
|
|
lastC = glyph.glyph_index;
|
|
}
|
|
|
|
if(glyph.draw) {
|
|
rectf pos = rectf::area(vec2f(x + (float)glyph.wOff, y + (float)glyph.hOff), vec2f(glyph.w, glyph.h));
|
|
rectf source = rectf::area(vec2f((float)glyph.x * xFactor, (float)glyph.y * yFactor),
|
|
vec2f((float)glyph.w * xFactor, (float)glyph.h * yFactor));
|
|
|
|
if(clipQuad(pos, source, clip ? &fclip : 0, font_verts, font_tc)) {
|
|
if(glyph.page != lastTexture) {
|
|
auto& mat = *textures[glyph.page];
|
|
buffer = VertexBufferTCV::fetch(&mat);
|
|
lastTexture = glyph.page;
|
|
}
|
|
|
|
auto* verts = buffer->request(1, PT_Quads);
|
|
|
|
for(unsigned i = 0; i < 4; ++i) {
|
|
auto& v = verts[i];
|
|
v.uv = font_tc[i];
|
|
v.col = col;
|
|
v.pos.set(font_verts[i].x, font_verts[i].y, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
x += glyph.xAdv;
|
|
}
|
|
}
|
|
}
|
|
|
|
vec2i drawChar(render::RenderDriver* driver, int c, int lastC,
|
|
int X, int Y, const Color* color, const recti* clip = 0) const {
|
|
float x = (float)X, y = (float)Y;
|
|
Color colors[4];
|
|
if(color)
|
|
colors[0] = colors[1] = colors[2] = colors[3] = *color;
|
|
|
|
rectf fclip;
|
|
if(clip)
|
|
fclip = rectf(*clip);
|
|
|
|
float xFactor = 1.f / float(textureWidth), yFactor = 1.f / float(textureHeight);
|
|
vec2f font_verts[4], font_tc[4];
|
|
|
|
FT_Activate_Size(size);
|
|
|
|
Glyph glyph;
|
|
|
|
if(c <= 0xff)
|
|
glyph = lowChars[c];
|
|
else {
|
|
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
|
if(g != hiChars.end() && g->letter == c) {
|
|
glyph = g->glyph;
|
|
}
|
|
else {
|
|
glyph = lowChars['?'];
|
|
}
|
|
}
|
|
|
|
//Don't kern anything involving digits
|
|
if(c >= '0' && c <= '9') {
|
|
lastC = 0;
|
|
}
|
|
else {
|
|
if(lastC) {
|
|
FT_Vector kerning;
|
|
FT_Get_Kerning(*face, lastC, glyph.glyph_index, 0, &kerning);
|
|
|
|
if(kerning.x != 0)
|
|
x += ((float)kerning.x) / 64.f;
|
|
}
|
|
|
|
lastC = glyph.glyph_index;
|
|
}
|
|
|
|
if(glyph.draw) {
|
|
rectf pos = rectf::area(vec2f(x + glyph.wOff, y + glyph.hOff), vec2f(glyph.w, glyph.h));
|
|
rectf source = rectf::area(vec2f((float)glyph.x * xFactor, (float)glyph.y * yFactor),
|
|
vec2f((float)glyph.w * xFactor, (float)glyph.h * yFactor));
|
|
|
|
if(clipQuad(pos, source, clip ? &fclip : 0, font_verts, font_tc))
|
|
driver->drawQuad(textures[glyph.page], font_verts, font_tc, color ? colors : 0);
|
|
}
|
|
|
|
x += glyph.xAdv;
|
|
return vec2i((int)x - X, (int)y - Y);
|
|
}
|
|
|
|
vec2i getDimension(const char* text) const {
|
|
vec2i dim(0, glyphHeight);
|
|
|
|
FT_Activate_Size(size);
|
|
|
|
fontChar lastC = 0;
|
|
u8it it(text);
|
|
unsigned maxWidth = 0;
|
|
|
|
while(fontChar c = it++) {
|
|
const Glyph* glyph = 0;
|
|
|
|
if(c == L'\n') {
|
|
if(dim.x > (int)maxWidth)
|
|
maxWidth = dim.x;
|
|
dim.x = 0;
|
|
dim.y += glyphHeight;
|
|
}
|
|
else if(c <= 0xff) {
|
|
glyph = &lowChars[c];
|
|
}
|
|
else {
|
|
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
|
if(g != hiChars.end() && g->letter == c)
|
|
glyph = &g->glyph;
|
|
else
|
|
glyph = &lowChars['?'];
|
|
}
|
|
|
|
if(glyph) {
|
|
dim.x += (int)glyph->xAdv;
|
|
|
|
//Don't kern anything involving digits
|
|
if(c < '0' || c > '9') {
|
|
if(lastC) {
|
|
FT_Vector kerning;
|
|
FT_Get_Kerning(*face, lastC, glyph->glyph_index, 0, &kerning);
|
|
|
|
if(kerning.x != 0)
|
|
dim.x += (unsigned)(kerning.x >> 6);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(dim.x < (int)maxWidth)
|
|
dim.x = maxWidth;
|
|
return dim;
|
|
}
|
|
|
|
vec2i getDimension(int c, int lastC) const {
|
|
vec2i dim(0, glyphHeight);
|
|
|
|
FT_Activate_Size(size);
|
|
|
|
const Glyph* glyph = 0;
|
|
|
|
if(c <= 0xff) {
|
|
glyph = &lowChars[c];
|
|
}
|
|
else {
|
|
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
|
if(g != hiChars.end() && g->letter == c)
|
|
glyph = &g->glyph;
|
|
else
|
|
glyph = &lowChars['?'];
|
|
}
|
|
|
|
if(glyph) {
|
|
dim.x += (int)glyph->xAdv;
|
|
|
|
//Don't kern anything involving digits
|
|
if(c < '0' || c > '9') {
|
|
if(lastC) {
|
|
FT_Vector kerning;
|
|
FT_Get_Kerning(*face, lastC, glyph->glyph_index, 0, &kerning);
|
|
|
|
if(kerning.x != 0)
|
|
dim.x += (unsigned)(kerning.x >> 6);
|
|
}
|
|
}
|
|
}
|
|
|
|
return dim;
|
|
}
|
|
|
|
unsigned getBaseline() const {
|
|
return baseLine;
|
|
}
|
|
|
|
unsigned getLineHeight() const {
|
|
return glyphHeight;
|
|
}
|
|
|
|
FontFT2()
|
|
: lowChars(new Glyph[256]), glyphHeight(0), baseLine(0), textureHeight(1), textureWidth(1)
|
|
{
|
|
memset(lowChars, 0, sizeof(Glyph) * 256);
|
|
}
|
|
|
|
~FontFT2()
|
|
{
|
|
FT_Done_Size(size);
|
|
delete[] lowChars;
|
|
for(auto tex = textures.begin(), end = textures.end(); tex != end; ++tex)
|
|
delete *tex;
|
|
}
|
|
};
|
|
|
|
FT_Library* library = 0;
|
|
std::map<std::string, FT_Face*> faces;
|
|
const int PAGE_SIZE = 512;
|
|
|
|
Font* loadFontFT2(render::RenderDriver& driver, const char* filename,
|
|
std::vector<std::pair<int,int>>& pages, int size) {
|
|
FontFT2* font = new FontFT2();
|
|
font->textureWidth = PAGE_SIZE;
|
|
font->textureHeight = PAGE_SIZE;
|
|
|
|
std::vector<Image*> images;
|
|
int cur_page = 0;
|
|
|
|
Image* img = new Image(PAGE_SIZE, PAGE_SIZE, FMT_Alpha, 0xff);
|
|
images.push_back(img);
|
|
|
|
int x = 0, y = 0;
|
|
|
|
//Create the library if none exists
|
|
if(!library) {
|
|
library = new FT_Library();
|
|
FT_Init_FreeType(library);
|
|
}
|
|
|
|
//Read in the font face if necessary
|
|
FT_Face* face = 0;
|
|
|
|
auto it = faces.find(filename);
|
|
if(it != faces.end()) {
|
|
face = it->second;
|
|
}
|
|
else {
|
|
face = new FT_Face();
|
|
if (int err = FT_New_Face(*library, filename, 0, face)) {
|
|
fprintf(stderr, "Error loading font %s (code %d).\n", filename, err);
|
|
return 0;
|
|
}
|
|
|
|
faces[filename] = face;
|
|
}
|
|
|
|
FT_New_Size(*face, &font->size);
|
|
FT_Activate_Size(font->size);
|
|
|
|
FT_Set_Char_Size(*face, 0, size << 6, 96, 96);
|
|
|
|
font->x_scale = (double)((*face)->size->metrics.x_scale / 65536.0);
|
|
font->y_scale = (double)((*face)->size->metrics.y_scale / 65536.0);
|
|
font->glyphHeight = (unsigned)((*face)->size->metrics.height >> 6);
|
|
font->face = face;
|
|
|
|
font->baseLine = font->glyphHeight + (unsigned)((*face)->size->metrics.descender >> 6);
|
|
int lineHeight = 0;
|
|
|
|
for(auto page = pages.begin(), end = pages.end(); page != end; ++page) {
|
|
int from = page->first;
|
|
int to = page->second;
|
|
|
|
for(int ch = from; ch < to; ++ch) {
|
|
int glyph_ind = FT_Get_Char_Index(*face, ch);
|
|
|
|
FontFT2::Glyph glyph;
|
|
glyph.page = cur_page;
|
|
|
|
if(glyph_ind != 0) {
|
|
FT_Load_Glyph(*face, glyph_ind, FT_LOAD_RENDER);
|
|
|
|
auto glp = (*face)->glyph;
|
|
auto bmp = glp->bitmap;
|
|
|
|
//Collect data about glyph
|
|
glyph.draw = true;
|
|
glyph.glyph_index = glyph_ind;
|
|
glyph.w = glp->bitmap.width;
|
|
glyph.h = glp->bitmap.rows;
|
|
glyph.wOff = glp->bitmap_left;
|
|
glyph.hOff = font->baseLine - glp->bitmap_top;
|
|
glyph.xAdv = ((float)glp->metrics.horiAdvance) / 64.f;
|
|
|
|
glyph.x = x;
|
|
glyph.y = y;
|
|
|
|
if(glyph.h > lineHeight)
|
|
lineHeight = glyph.h;
|
|
|
|
//Find a fitting spot on the image
|
|
x += (unsigned)glyph.w + 2;
|
|
if(x > PAGE_SIZE) {
|
|
x = (unsigned)glyph.w + 2;
|
|
glyph.x = 0;
|
|
|
|
y += lineHeight + 1;
|
|
glyph.y += lineHeight + 1;
|
|
lineHeight = glyph.h;
|
|
|
|
if(y + font->glyphHeight + 1 > (unsigned)PAGE_SIZE) {
|
|
img = new Image(PAGE_SIZE, PAGE_SIZE, FMT_Alpha, 0xff);
|
|
images.push_back(img);
|
|
|
|
y = 0;
|
|
glyph.y = 0;
|
|
|
|
++cur_page;
|
|
++glyph.page;
|
|
}
|
|
}
|
|
|
|
|
|
//Put the glyph on the actual image
|
|
unsigned char* buffer = bmp.buffer;
|
|
for(int row = 0; row < bmp.rows; ++row, buffer += bmp.width)
|
|
memcpy(&img->get_alpha(glyph.x, glyph.y + row), buffer, bmp.width);
|
|
}
|
|
else {
|
|
glyph.draw = false;
|
|
glyph.xAdv = 0;
|
|
}
|
|
|
|
if(ch <= 0xff)
|
|
font->lowChars[ch] = glyph;
|
|
else
|
|
font->hiChars.push_back(FontFT2::GlyphEntry(ch, glyph));
|
|
}
|
|
}
|
|
|
|
//Create the textures
|
|
for(auto it = images.begin(), end = images.end(); it != end; ++it) {
|
|
auto material = new render::RenderState();
|
|
material->depthTest = render::DT_NoDepthTest;
|
|
material->lighting = false;
|
|
material->culling = render::FC_None;
|
|
material->baseMat = MAT_Font;
|
|
material->depthWrite = false;
|
|
if(load_resources)
|
|
material->textures[0] = resource::queueImage(*it, 20, true, false);
|
|
|
|
font->textures.push_back(material);
|
|
}
|
|
|
|
std::sort(font->hiChars.begin(), font->hiChars.end());
|
|
return font;
|
|
}
|
|
|
|
};
|