From 2abfd302fa038a370830311861c78054fc4d4b2b Mon Sep 17 00:00:00 2001 From: Daniel Scharrer Date: Fri, 20 Apr 2012 14:31:12 +0200 Subject: [PATCH] Font: Fix uploading missing glyphs during font rendering. (ref bug #226) --- src/graphics/font/Font.cpp | 143 +++++++++++++------- src/graphics/font/Font.h | 39 +++++- src/graphics/texture/PackedTexture.cpp | 179 ++++++++++--------------- src/graphics/texture/PackedTexture.h | 48 ++++--- src/graphics/texture/Texture.cpp | 2 +- 5 files changed, 233 insertions(+), 178 deletions(-) diff --git a/src/graphics/font/Font.cpp b/src/graphics/font/Font.cpp index ded8554a24..7208d0be9c 100644 --- a/src/graphics/font/Font.cpp +++ b/src/graphics/font/Font.cpp @@ -47,13 +47,13 @@ Font::Font(const res::path & fontFile, unsigned int fontSize, FT_Face face) // Insert all the glyphs into texture pages m_Textures = new PackedTexture(TEXTURE_SIZE, Image::Format_A8); - m_Textures->BeginPacking(); - for(unsigned int chr = 32; chr < 256; ++chr) { - InsertGlyph(chr); + // Pre-load common glyphs + for(u32 chr = 32; chr < 256; ++chr) { + insertGlyph(chr); } - m_Textures->EndPacking(); + m_Textures->upload(); } Font::~Font() { @@ -64,36 +64,45 @@ Font::~Font() { FT_Done_Face(m_FTFace); } -bool Font::InsertGlyph(unsigned int character) { +void Font::insertPlaceholderGlyph(u32 character) { + + // Glyp does not exist - insert something so we don't look it up for every render pass. + if(character < 256) { + + // ignore non-displayable ANSI characters (and some more) + Glyph & glyph = m_Glyphs[character]; + glyph.size = Vec2i::ZERO; + glyph.advance = Vec2f::ZERO; + glyph.lsb_delta = glyph.rsb_delta = 0; + glyph.draw_offset = Vec2i::ZERO; + glyph.uv_start = Vec2f::ZERO; + glyph.uv_end = Vec2f::ZERO; + glyph.texture = 0; + + } else { + m_Glyphs[character] = m_Glyphs['?']; + LogWarning << "No glyph for character U+" << std::hex << character; + } +} + +bool Font::insertGlyph(u32 character) { FT_Error error; FT_UInt glyphIndex = FT_Get_Char_Index(m_FTFace, character); if(!glyphIndex) { - // Glyp does not exist - inser something so we don't look it up for every render pass. - if(character < 256) { - // ignore non-displayable ANSI characters (and some more) - Glyph & glyph = m_Glyphs[character]; - glyph.size = Vec2i::ZERO; - glyph.advance = Vec2f::ZERO; - glyph.lsb_delta = glyph.rsb_delta = 0; - glyph.draw_offset = Vec2i::ZERO; - glyph.uv_start = Vec2f::ZERO; - glyph.uv_end = Vec2f::ZERO; - glyph.texture = 0; - } else { - m_Glyphs[character] = m_Glyphs['?']; - LogWarning << "No glyph for character U+" << std::hex << character; - } + insertPlaceholderGlyph(character); return false; } error = FT_Load_Glyph(m_FTFace, glyphIndex, FT_LOAD_FORCE_AUTOHINT); if(error) { + insertPlaceholderGlyph(character); return false; } error = FT_Render_Glyph(m_FTFace->glyph, FT_RENDER_MODE_NORMAL); if(error) { + insertPlaceholderGlyph(character); return false; } @@ -125,16 +134,15 @@ bool Font::InsertGlyph(unsigned int character) { unsigned char* dst = imgGlyph.GetData(); memcpy(dst, src, glyph.size.x * glyph.size.y); - int glyphPosX; - int glyphPosY; - m_Textures->InsertImage(imgGlyph, glyphPosX, glyphPosY, glyph.texture); + Vec2i offset; + m_Textures->insertImage(imgGlyph, glyph.texture, offset); // Compute UV mapping for each glyph. - const float TEXTURE_SIZE = m_Textures->GetTextureSize(); - glyph.uv_start.x = glyphPosX / TEXTURE_SIZE; - glyph.uv_start.y = glyphPosY / TEXTURE_SIZE; - glyph.uv_end.x = (glyphPosX + glyph.size.x) / TEXTURE_SIZE; - glyph.uv_end.y = (glyphPosY + glyph.size.y) / TEXTURE_SIZE; + const float textureSize = m_Textures->getTextureSize(); + glyph.uv_start.x = offset.x / textureSize; + glyph.uv_start.y = offset.y / textureSize; + glyph.uv_end.x = (offset.x + glyph.size.x) / textureSize; + glyph.uv_end.y = (offset.y + glyph.size.y) / textureSize; } return true; @@ -144,8 +152,8 @@ bool Font::WriteToDisk() { bool ok = true; - for(unsigned int i = 0; i < m_Textures->GetTextureCount(); ++i) { - Texture2D & tex = m_Textures->GetTexture(i); + for(unsigned int i = 0; i < m_Textures->getTextureCount(); ++i) { + Texture2D & tex = m_Textures->getTexture(i); std::stringstream ss; ss << m_FTFace->family_name; @@ -165,7 +173,7 @@ bool Font::WriteToDisk() { return ok; } -inline static bool read_utf8(string::const_iterator & it, string::const_iterator end, u32 & chr) { +inline static bool read_utf8(Font::text_iterator & it, Font::text_iterator end, u32 & chr) { if(it == end) { return false; @@ -208,11 +216,56 @@ inline static bool read_utf8(string::const_iterator & it, string::const_iterator return true; } -void Font::Draw(int x, int y, const std::string& str, Color color) { - Draw(x, y, str.begin(), str.end(), color); +bool Font::insertMissingGlyphs(text_iterator begin, text_iterator end) { + + u32 chr; + bool changed = false; + + for(text_iterator it = begin; !read_utf8(it, end, chr); ) { + if(m_Glyphs.find(chr) == m_Glyphs.end()) { + if(chr >= 256 && insertGlyph(chr)) { + changed = true; + } + } + } + + return changed; } -void Font::Draw(int x, int y, std::string::const_iterator itStart, std::string::const_iterator itEnd, Color color) { +Font::glyph_iterator Font::getNextGlyph(text_iterator & it, text_iterator end) { + + u32 chr; + if(!read_utf8(it, end, chr)) { + return m_Glyphs.end(); + } + + glyph_iterator glyph = m_Glyphs.find(chr); + if(glyph != m_Glyphs.end()) { + return glyph; // an existing glyph + } + + if(chr < 256) { + // We pre-load all glyphs for charactres < 256, se there is no point in checking again + return m_Glyphs.end(); + } + + if(!insertGlyph(chr)) { + // No new glyph was inserted but the character was mapped to an existing one + return m_Glyphs.find(chr); + } + + arx_assert(m_Glyphs.find(chr) != m_Glyphs.end()); + + // As we need to re-upload the textures now, first check for more missing glyphs + insertMissingGlyphs(it, end); + + // Re-upload the changed textures + m_Textures->upload(); + + return m_Glyphs.find(chr); // the newly inserted glyph +} + +void Font::Draw(int x, int y, text_iterator itStart, text_iterator itEnd, Color color) { GRenderer->SetRenderState(Renderer::Lighting, false); GRenderer->SetRenderState(Renderer::AlphaBlending, true); @@ -245,17 +298,15 @@ void Font::Draw(int x, int y, std::string::const_iterator itStart, std::string:: FT_Pos prevRsbDelta = 0; u32 chr; - for(string::const_iterator it = itStart; read_utf8(it, itEnd, chr); ) { + for(text_iterator it = itStart; it != itEnd; ) { // Get glyph in glyph map - std::map::const_iterator itGlyph = m_Glyphs.find(chr); + glyph_iterator itGlyph = getNextGlyph(it, itEnd); if(itGlyph == m_Glyphs.end()) { - if(chr < 256 || !InsertGlyph(chr) || (itGlyph = m_Glyphs.find(chr)) == m_Glyphs.end()) { - continue; - } + continue; } - const Glyph & glyph = (*itGlyph).second; + const Glyph & glyph = itGlyph->second; // Kerning if(FT_HAS_KERNING(m_FTFace)) { @@ -277,7 +328,7 @@ void Font::Draw(int x, int y, std::string::const_iterator itStart, std::string:: prevRsbDelta = glyph.rsb_delta; // Draw - GRenderer->SetTexture(0, &m_Textures->GetTexture(glyph.texture)); + GRenderer->SetTexture(0, &m_Textures->getTexture(glyph.texture)); GRenderer->DrawTexturedRect(((int)penX) + glyph.draw_offset.x, ((int)penY) - glyph.draw_offset.y, glyph.size.x, -glyph.size.y, glyph.uv_start.x, glyph.uv_end.y, glyph.uv_end.x, glyph.uv_start.y, color); // Advance @@ -303,7 +354,7 @@ Vec2i Font::GetTextSize(const std::string & str) { return GetTextSize(str.begin(), str.end()); } -Vec2i Font::GetTextSize(std::string::const_iterator itStart, std::string::const_iterator itEnd) { +Vec2i Font::GetTextSize(text_iterator itStart, text_iterator itEnd) { FT_UInt currentGlyph; FT_UInt previousGlyph = 0; @@ -314,16 +365,16 @@ Vec2i Font::GetTextSize(std::string::const_iterator itStart, std::string::const_ FT_Pos prevRsbDelta = 0; u32 chr; - for(string::const_iterator it = itStart; read_utf8(it, itEnd, chr); ) { + for(text_iterator it = itStart; read_utf8(it, itEnd, chr); ) { - std::map::const_iterator itGlyph = m_Glyphs.find(chr); + glyph_iterator itGlyph = m_Glyphs.find(chr); if(itGlyph == m_Glyphs.end()) { - if(chr < 256 || !InsertGlyph(chr) || (itGlyph = m_Glyphs.find(chr)) == m_Glyphs.end()) { + if(chr < 256 || !insertGlyph(chr) || (itGlyph = m_Glyphs.find(chr)) == m_Glyphs.end()) { continue; } } - const Glyph & glyph = (*itGlyph).second; + const Glyph & glyph = itGlyph->second; // Kerning if(FT_HAS_KERNING(m_FTFace)) { diff --git a/src/graphics/font/Font.h b/src/graphics/font/Font.h index ce30108a6a..4ab9158def 100644 --- a/src/graphics/font/Font.h +++ b/src/graphics/font/Font.h @@ -72,18 +72,24 @@ class Font : private boost::noncopyable { public: + typedef std::string::const_iterator text_iterator; + const Info & GetInfo() const { return m_Info; } const res::path & GetName() const { return m_Info.m_Name; } unsigned int GetSize() const { return m_Info.m_Size; } - void Draw(const Vector2 &p, const std::string &str, const Color &color) { + void Draw(const Vector2 & p, const std::string & str, const Color & color) { Draw(p.x, p.y, str, color); } - void Draw(int pX, int pY, const std::string & str, Color color); - void Draw(int pX, int pY, std::string::const_iterator itStart, std::string::const_iterator itEnd, Color color); + + void Draw(int pX, int pY, const std::string & str, Color color) { + Draw(pX, pY, str.begin(), str.end(), color); + } + + void Draw(int pX, int pY, text_iterator itStart, text_iterator itEnd, Color color); Vec2i GetTextSize(const std::string & str); - Vec2i GetTextSize(std::string::const_iterator itStart, std::string::const_iterator itEnd); + Vec2i GetTextSize(text_iterator itStart, text_iterator itEnd); int GetLineHeight() const; @@ -97,7 +103,22 @@ class Font : private boost::noncopyable { Font(const res::path & fontFile, unsigned int fontSize, struct FT_FaceRec_ * face); ~Font(); - bool InsertGlyph(unsigned int character); + //! Maps the given character to a placeholder glyph + void insertPlaceholderGlyph(u32 character); + + /*! + * Inserts a single glyph + * Always maps the character to a glyph - uses a placeholder if there + * is no glyph for the given character + * \return true if the glyph textures were changed + */ + bool insertGlyph(u32 character); + + /*! + * Inserts any missing glyphs for the characters in the UTF-8 string [begin, end) + * \return true if the glyph textures were changed + */ + bool insertMissingGlyphs(text_iterator begin, text_iterator end); private: @@ -106,6 +127,14 @@ class Font : private boost::noncopyable { struct FT_FaceRec_ * m_FTFace; std::map m_Glyphs; + typedef std::map::const_iterator glyph_iterator; + + /*! + * Parses UTF-8 input and returns the glyph for the first character + * Inserts missing glyphs if possible. + * \return a glyph iterator or m_Glyphs.end() + */ + glyph_iterator getNextGlyph(text_iterator & it, text_iterator end); class PackedTexture * m_Textures; diff --git a/src/graphics/texture/PackedTexture.cpp b/src/graphics/texture/PackedTexture.cpp index ee22c97b30..e22128b263 100644 --- a/src/graphics/texture/PackedTexture.cpp +++ b/src/graphics/texture/PackedTexture.cpp @@ -22,160 +22,138 @@ #include "graphics/Renderer.h" #include "graphics/texture/Texture.h" -PackedTexture::PackedTexture(unsigned int pSize, Image::Format pFormat) : mTexSize(pSize), mTexFormat(pFormat) { } +PackedTexture::PackedTexture(unsigned int pSize, Image::Format pFormat) + : textureSize(pSize), textureFormat(pFormat) { } PackedTexture::~PackedTexture() { - ClearAll(); + clear(); } -void PackedTexture::ClearAll() { - - for(unsigned int i = 0; i < mTexTrees.size(); i++) { - delete mTexTrees[i]; +void PackedTexture::clear() { + textures.clear(); +} + +void PackedTexture::upload() { + for(texture_iterator i = textures.begin(); i != textures.end(); ++i) { + TextureTree * tree = *i; + if(tree->dirty) { + tree->texture->Restore(); + tree->dirty = false; + } } - mTexTrees.clear(); +} + +PackedTexture::TextureTree::TextureTree(unsigned int textureSize, + Image::Format textureFormat) { - for(unsigned int i = 0; i < mImages.size(); i++) { - delete mImages[i]; - } - mImages.clear(); + root.rect = Rect(0, 0, textureSize - 1, textureSize - 1); - for(unsigned int i = 0; i < mTextures.size(); i++) { - delete mTextures[i]; - } - mTextures.clear(); + texture = GRenderer->CreateTexture2D(); + texture->Init(textureSize, textureSize, textureFormat); + texture->GetImage().Clear(); + dirty = true; } -void PackedTexture::BeginPacking() { - ClearAll(); +PackedTexture::TextureTree::~TextureTree() { + delete texture; } -void PackedTexture::EndPacking() { - - // Trees are now useless. - for(unsigned int i = 0; i < mTexTrees.size(); i++) { - delete mTexTrees[i]; - } - mTexTrees.clear(); +PackedTexture::TextureTree::Node * PackedTexture::TextureTree::insertImage(const Image & img) { - mTextures.resize(mImages.size()); + Node * node = root.insertImage(img); - // Create a texture with each images. - for(unsigned int i = 0; i < mTextures.size(); i++) { - - mTextures[i] = GRenderer->CreateTexture2D(); - mTextures[i]->Init(*mImages[i], 0); - - // Images are not needed anymore. - delete mImages[i]; + if(node != NULL) { + texture->GetImage().Copy(img, node->rect.left, node->rect.top); + dirty = true; } - mImages.clear(); + return node; } -bool PackedTexture::InsertImage(const Image & pImg, int & pOffsetU, int & pOffsetV, unsigned int & pTextureIndex) { +bool PackedTexture::insertImage(const Image & image, unsigned int & textureIndex, + Vec2i & offset) { // Validate image size - if(pImg.GetWidth() > mTexSize || pImg.GetHeight() > mTexSize) { + if(image.GetWidth() > textureSize || image.GetHeight() > textureSize) { return false; } - // Copy to one of the existing image + // Copy to one of the existing textures TextureTree::Node * node = NULL; unsigned int nodeTree = 0; - for(unsigned int i = 0; i < mTexTrees.size(); i++) { - node = mTexTrees[i]->InsertImage( pImg ); + for(size_t i = 0; i < textures.size(); i++) { + node = textures[i]->insertImage(image); nodeTree = i; } - // No space found, create a new tree + // No space found, create a new texture if(!node) { - - mTexTrees.push_back(new TextureTree(mTexSize)); - - Image* newPage = new Image(); - newPage->Create(mTexSize, mTexSize, mTexFormat); - newPage->Clear(); - - mImages.push_back(newPage); - - node = mTexTrees[mTexTrees.size() - 1]->InsertImage(pImg); - nodeTree = mTexTrees.size() - 1; + textures.push_back(new TextureTree(textureSize, textureFormat)); + node = textures[textures.size() - 1]->insertImage(image); + nodeTree = textures.size() - 1; } - // A node must have been found. + // A node must have been found arx_assert(node); // Copy texture there if(node) { - - mImages[nodeTree]->Copy( pImg, node->mRect.left, node->mRect.top ); - - // Copy values back into info structure. - pOffsetU = node->mRect.left; - pOffsetV = node->mRect.top; - pTextureIndex = nodeTree; + // Copy values back into info structure + offset = node->rect.origin; + textureIndex = nodeTree; } return node != NULL; } -Texture2D& PackedTexture::GetTexture(unsigned int pTexture) { - arx_assert(pTexture < mTextures.size()); - arx_assert(mTextures[pTexture]); - return *mTextures[pTexture]; -} - -unsigned int PackedTexture::GetTextureCount() const { - return mTextures.size(); -} - -unsigned int PackedTexture::GetTextureSize() const { - return mTexSize; +Texture2D& PackedTexture::getTexture(unsigned int index) { + arx_assert(index < textures.size()); + arx_assert(textures[index]->texture); + return *textures[index]->texture; } PackedTexture::TextureTree::Node::Node() { - mChilds[0] = NULL; - mChilds[1] = NULL; - mInUse = 0; + children[0] = NULL; + children[1] = NULL; + used = 0; } PackedTexture::TextureTree::Node::~Node() { - if(mChilds[0]) { - delete mChilds[0]; + if(children[0]) { + delete children[0]; } - if(mChilds[1]) { - delete mChilds[1]; + if(children[1]) { + delete children[1]; } } -PackedTexture::TextureTree::Node * PackedTexture::TextureTree::Node::InsertImage(const Image & pImg) { +PackedTexture::TextureTree::Node * PackedTexture::TextureTree::Node::insertImage(const Image & image) { Node * result = NULL; // We're in a full node/leaf, return immediately. - if(mInUse) { + if(used) { return NULL; } // If we're not a leaf, try inserting in childs - if(mChilds[0]) { + if(children[0]) { - result = mChilds[0]->InsertImage(pImg); + result = children[0]->insertImage(image); if(!result) { - result = mChilds[1]->InsertImage(pImg); + result = children[1]->insertImage(image); } - mInUse = mChilds[0]->mInUse && mChilds[1]->mInUse; + used = children[0]->used && children[1]->used; return result; } - int diffW = (mRect.width()+1) - pImg.GetWidth(); - int diffH = (mRect.height()+1) - pImg.GetHeight(); + int diffW = (rect.width() + 1) - image.GetWidth(); + int diffH = (rect.height() + 1) - image.GetHeight(); // If we're too small, return. if(diffW < 0 || diffH < 0) { @@ -184,33 +162,22 @@ PackedTexture::TextureTree::Node * PackedTexture::TextureTree::Node::InsertImage // Perfect match ! if(diffW == 0 && diffH == 0) { - mInUse = true; + used = true; return this; } - // Otherwise, gotta split this node and create some kids. - mChilds[0] = new Node(); - mChilds[1] = new Node(); + // Otherwise, gotta split this node and create some kids + children[0] = new Node(); + children[1] = new Node(); if(diffW > diffH) { - mChilds[0]->mRect = Rect(mRect.left, mRect.top, mRect.left + pImg.GetWidth() - 1, mRect.bottom); - mChilds[1]->mRect = Rect(mRect.left + pImg.GetWidth(), mRect.top, mRect.right, mRect.bottom); + children[0]->rect = Rect(rect.left, rect.top, rect.left + image.GetWidth() - 1, rect.bottom); + children[1]->rect = Rect(rect.left + image.GetWidth(), rect.top, rect.right, rect.bottom); } else { - mChilds[0]->mRect = Rect(mRect.left, mRect.top, mRect.right, mRect.top + pImg.GetHeight() - 1); - mChilds[1]->mRect = Rect(mRect.left, mRect.top + pImg.GetHeight(), mRect.right, mRect.bottom); + children[0]->rect = Rect(rect.left, rect.top, rect.right, rect.top + image.GetHeight() - 1); + children[1]->rect = Rect(rect.left, rect.top + image.GetHeight(), rect.right, rect.bottom); } // Insert into first child we created - return mChilds[0]->InsertImage(pImg); -} - -PackedTexture::TextureTree::TextureTree(unsigned int pSize) { - mRoot.mRect.left = 0; - mRoot.mRect.right = pSize-1; - mRoot.mRect.top = 0; - mRoot.mRect.bottom = pSize-1; -} - -PackedTexture::TextureTree::Node * PackedTexture::TextureTree::InsertImage(const Image & pImg) { - return mRoot.InsertImage( pImg ); + return children[0]->insertImage(image); } diff --git a/src/graphics/texture/PackedTexture.h b/src/graphics/texture/PackedTexture.h index 865ce2c99b..0291bf68d8 100644 --- a/src/graphics/texture/PackedTexture.h +++ b/src/graphics/texture/PackedTexture.h @@ -21,9 +21,11 @@ #define ARX_GRAPHICS_TEXTURE_PACKEDTEXTURE_H #include +#include #include "graphics/image/Image.h" #include "math/Rectangle.h" +#include "math/Vector2.h" class Texture2D; @@ -31,20 +33,21 @@ class PackedTexture { public: - PackedTexture(unsigned int pSize, Image::Format pFormat); + PackedTexture(unsigned int textureSize, Image::Format pFormat); ~PackedTexture(); - void ClearAll(); + //! Reset the packed texture - remove all images + void clear(); - void BeginPacking(); - void EndPacking(); + //! Upload changed textures + void upload(); - bool InsertImage(const Image& pImg, int & pOffsetU, int & pOffsetV, unsigned int & pTextureIndex); + bool insertImage(const Image & image, unsigned int & textureIndex, Vec2i & offset); - Texture2D & GetTexture(unsigned int pTexture); + Texture2D & getTexture(unsigned int index); - unsigned int GetTextureSize() const; - unsigned int GetTextureCount() const; + unsigned int getTextureSize() const { return textureSize; } + size_t getTextureCount() const { return textures.size(); } protected: @@ -57,30 +60,35 @@ class PackedTexture { Node(); ~Node(); - Node * InsertImage(const Image & pImg); + Node * insertImage(const Image & pImg); - Node * mChilds[2]; - Rect mRect; - bool mInUse; + Node * children[2]; + Rect rect; + bool used; }; - explicit TextureTree(unsigned int pSize); - Node * InsertImage(const Image & pImg); + explicit TextureTree(unsigned int textureSize, Image::Format textureFormat); + ~TextureTree(); + + Node * insertImage(const Image & pImg); private: - Node mRoot; + Node root; + + public: + Texture2D * texture; + bool dirty; }; private: - std::vector mImages; - std::vector mTextures; - std::vector mTexTrees; + std::vector textures; + typedef std::vector::iterator texture_iterator; - unsigned int mTexSize; - Image::Format mTexFormat; + const unsigned int textureSize; + Image::Format textureFormat; }; diff --git a/src/graphics/texture/Texture.cpp b/src/graphics/texture/Texture.cpp index f47dafc883..4c23c5990e 100644 --- a/src/graphics/texture/Texture.cpp +++ b/src/graphics/texture/Texture.cpp @@ -38,7 +38,7 @@ bool Texture2D::Init(unsigned int pWidth, unsigned int pHeight, Image::Format pF mFileName.clear(); - size = Vec2i(pWidth, pHeight); + size = Vec2i(pWidth, pHeight); mImage.Create(pWidth, pHeight, pFormat); mFormat = pFormat; flags = 0;