/* Copyright (c) 2010 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * Unicode OpenGL texture font. */ #include "precompiled.h" #include "unifont.h" #include #include #include #include #include "ogl_tex.h" #include "lib/res/h_mgr.h" struct GlyphData { float u0, v0, u1, v1; i16 x0, y0, x1, y1; i16 xadvance; }; typedef std::map glyphmap; static glyphmap* BoundGlyphs = NULL; struct UniFont { Handle ht; // Handle to font texture glyphmap* glyphs; int LineSpacing; int Height; // of a capital letter, roughly }; H_TYPE_DEFINE(UniFont); static void UniFont_init(UniFont* UNUSED(f), va_list UNUSED(args)) { } static void UniFont_dtor(UniFont* f) { // these are all safe, no is_valid flags needed (void)ogl_tex_free(f->ht); SAFE_DELETE(f->glyphs); } // basename is e.g. "console"; the files are "fonts/console.fnt" and "fonts/console.png" // [10..70ms] static Status UniFont_reload(UniFont* f, const PIVFS& vfs, const VfsPath& basename, Handle UNUSED(h)) { // already loaded if(f->ht > 0) return INFO::OK; f->glyphs = new glyphmap(); const VfsPath path(L"fonts/"); // Read font definition file into a stringstream shared_ptr buf; size_t size; const VfsPath fntName(basename.ChangeExtension(L".fnt")); RETURN_STATUS_IF_ERR(vfs->LoadFile(path / fntName, buf, size)); // [cumulative for 12: 36ms] std::istringstream FNTStream(std::string((const char*)buf.get(), size)); int Version; FNTStream >> Version; if (Version < 100 || Version > 101) // Make sure this is from a recent version of the font builder WARN_RETURN(ERR::FAIL); int TextureWidth, TextureHeight; FNTStream >> TextureWidth >> TextureHeight; GLenum fmt_ovr = GL_ALPHA; if (Version >= 101) { std::string Format; FNTStream >> Format; if (Format == "rgba") fmt_ovr = GL_RGBA; else if (Format == "a") fmt_ovr = GL_ALPHA; else debug_warn(L"Invalid .fnt format string"); } int NumGlyphs; FNTStream >> NumGlyphs; FNTStream >> f->LineSpacing; if (Version >= 101) FNTStream >> f->Height; else f->Height = 0; // [cumulative for 12: 256ms] for (int i = 0; i < NumGlyphs; ++i) { int Codepoint, TextureX, TextureY, Width, Height, OffsetX, OffsetY, Advance; FNTStream >> Codepoint>>TextureX>>TextureY>>Width>>Height>>OffsetX>>OffsetY>>Advance; if (Codepoint < 0 || Codepoint > 0xFFFF) { DEBUG_WARN_ERR(ERR::LOGIC); // Invalid codepoint continue; } if (Version < 101 && Codepoint == 'I') { f->Height = Height; } GLfloat u = (GLfloat)TextureX / (GLfloat)TextureWidth; GLfloat v = (GLfloat)TextureY / (GLfloat)TextureHeight; GLfloat w = (GLfloat)Width / (GLfloat)TextureWidth; GLfloat h = (GLfloat)Height / (GLfloat)TextureHeight; GlyphData g = { u, -v, u+w, -v+h, (i16)OffsetX, (i16)-OffsetY, (i16)(OffsetX+Width), (i16)(-OffsetY+Height), (i16)Advance }; (*f->glyphs)[(u16)Codepoint] = g; } ENSURE(f->Height); // Ensure the height has been found (which should always happen if the font includes an 'I') // Load glyph texture // [cumulative for 12: 20ms] const VfsPath imgName(basename.ChangeExtension(L".png")); Handle ht = ogl_tex_load(vfs, path / imgName); RETURN_STATUS_IF_ERR(ht); (void)ogl_tex_set_filter(ht, GL_NEAREST); // override is necessary because the GL format is chosen as LUMINANCE, // but we want ALPHA. there is no way of knowing what format // 8bpp textures are in - we could adopt a naming convention and // add some TEX_ flags, but that's overkill. Status err = ogl_tex_upload(ht, fmt_ovr); if(err < 0) { (void)ogl_tex_free(ht); return err; } f->ht = ht; return INFO::OK; } static Status UniFont_validate(const UniFont* f) { if(f->ht < 0) WARN_RETURN(ERR::_1); if(debug_IsPointerBogus(f->glyphs)) WARN_RETURN(ERR::_2); // and are read directly from font file. // negative values don't make sense, but that's all we can check. if(f->LineSpacing < 0 || f->Height < 0) WARN_RETURN(ERR::_3); return INFO::OK; } static Status UniFont_to_string(const UniFont* f, wchar_t* buf) { if (f->ht) // not true if this is called after dtor (which it is) { const VfsPath& path = h_filename(f->ht); swprintf_s(buf, H_STRING_LEN, L"Font %ls", path.string().c_str()); } else swprintf_s(buf, H_STRING_LEN, L"Font"); return INFO::OK; } Handle unifont_load(const PIVFS& vfs, const VfsPath& pathname, size_t flags) { return h_alloc(H_UniFont, vfs, pathname, flags); } Status unifont_unload(Handle& h) { H_DEREF(h, UniFont, f); // unbind ourself, so people will get errors if // they draw more text without binding a new font if (BoundGlyphs == f->glyphs) BoundGlyphs = NULL; return h_free(h, H_UniFont); } Status unifont_bind(const Handle h) { H_DEREF(h, UniFont, f); ogl_tex_bind(f->ht); BoundGlyphs = f->glyphs; return INFO::OK; } int unifont_linespacing(const Handle h) { H_DEREF(h, UniFont, f); return f->LineSpacing; } int unifont_height(const Handle h) { H_DEREF(h, UniFont, f); return f->Height; } int unifont_character_width(const Handle h, wchar_t c) { H_DEREF(h, UniFont, f); glyphmap::iterator it = f->glyphs->find(c); if (it == f->glyphs->end()) it = f->glyphs->find(0xFFFD); // Use the missing glyph symbol return it->second.xadvance; } struct t2f_v2i { float u, v; i16 x, y; }; void glvwprintf(const wchar_t* fmt, va_list args) { const int buf_size = 1024; wchar_t buf[buf_size]; int ret = vswprintf(buf, buf_size-1, fmt, args); if(ret < 0) { debug_printf(L"glwprintf failed (buffer size exceeded?) - return value %d, errno %d\n", ret, errno); } // Make sure there's always null termination buf[buf_size-1] = 0; ENSURE(BoundGlyphs != NULL); // You always need to bind something first // Count the number of characters size_t len = wcslen(buf); // 0 glyphs -> nothing to do (avoid BoundsChecker warning) if (!len) return; t2f_v2i* vertexes = new t2f_v2i[len*4]; i32 x = 0; for (size_t i = 0; i < len; ++i) { glyphmap::iterator it = BoundGlyphs->find(buf[i]); if (it == BoundGlyphs->end()) it = BoundGlyphs->find(0xFFFD); // Use the missing glyph symbol if (it == BoundGlyphs->end()) // Missing the missing glyph symbol - give up continue; const GlyphData& g = it->second; vertexes[i*4].u = g.u0; vertexes[i*4].v = g.v0; vertexes[i*4].x = g.x0 + x; vertexes[i*4].y = g.y0; vertexes[i*4+1].u = g.u1; vertexes[i*4+1].v = g.v0; vertexes[i*4+1].x = g.x1 + x; vertexes[i*4+1].y = g.y0; vertexes[i*4+2].u = g.u1; vertexes[i*4+2].v = g.v1; vertexes[i*4+2].x = g.x1 + x; vertexes[i*4+2].y = g.y1; vertexes[i*4+3].u = g.u0; vertexes[i*4+3].v = g.v1; vertexes[i*4+3].x = g.x0 + x; vertexes[i*4+3].y = g.y1; x += g.xadvance; } ogl_WarnIfError(); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glVertexPointer(2, GL_SHORT, sizeof(t2f_v2i), (u8*)vertexes + offsetof(t2f_v2i, x)); glTexCoordPointer(2, GL_FLOAT, sizeof(t2f_v2i), (u8*)vertexes + offsetof(t2f_v2i, u)); glDrawArrays(GL_QUADS, 0, (GLsizei)len*4); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); ogl_WarnIfError(); // Move into position for subsequent prints glTranslatef((float)x, 0, 0); delete[] vertexes; } void glwprintf(const wchar_t* fmt, ...) { va_list args; va_start(args, fmt); glvwprintf(fmt, args); va_end(args); } Status unifont_stringsize(const Handle h, const wchar_t* text, int& width, int& height) { H_DEREF(h, UniFont, f); width = 0; height = f->Height; size_t len = wcslen(text); for (size_t i = 0; i < len; ++i) { glyphmap::iterator it = f->glyphs->find(text[i]); if (it == f->glyphs->end()) it = f->glyphs->find(0xFFFD); // Use the missing glyph symbol if (it == f->glyphs->end()) // Missing the missing glyph symbol - give up { DEBUG_WARN_ERR(ERR::LOGIC); // Missing the missing glyph in a unifont! return INFO::OK; } width += it->second.xadvance; // Add the character's advance distance } return INFO::OK; }