// // Copyright (c) 2008-2017 the Urho3D project. // // 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. // #include "../../Precompiled.h" #include "../../Core/Context.h" #include "../../Graphics/Graphics.h" #include "../../Graphics/Texture2D.h" #include "../../IO/File.h" #include "../../IO/FileSystem.h" #include "../../IO/Log.h" #include "../../IO/MemoryBuffer.h" #include "../../Resource/ResourceCache.h" #include "../../Resource/XMLFile.h" #include "Text3DFont.h" #include "Text3DBitmap.h" #include "../../DebugNew.h" namespace Atomic { Text3DBitmap::Text3DBitmap(Text3DFont* font) : Text3DFontFace(font) { } Text3DBitmap::~Text3DBitmap() { } bool Text3DBitmap::Load(const unsigned char* fontData, unsigned fontDataSize, float pointSize) { Context* context = font_->GetContext(); SharedPtr xmlReader(new XMLFile(context)); MemoryBuffer memoryBuffer(fontData, fontDataSize); if (!xmlReader->Load(memoryBuffer)) { ATOMIC_LOGERROR("Could not load XML file"); return false; } XMLElement root = xmlReader->GetRoot("font"); if (root.IsNull()) { ATOMIC_LOGERROR("Could not find Font element"); return false; } XMLElement pagesElem = root.GetChild("pages"); if (pagesElem.IsNull()) { ATOMIC_LOGERROR("Could not find Pages element"); return false; } XMLElement infoElem = root.GetChild("info"); if (!infoElem.IsNull()) pointSize_ = infoElem.GetInt("size"); XMLElement commonElem = root.GetChild("common"); rowHeight_ = commonElem.GetInt("lineHeight"); unsigned pages = commonElem.GetUInt("pages"); textures_.Reserve(pages); ResourceCache* resourceCache = font_->GetSubsystem(); String fontPath = GetPath(font_->GetName()); unsigned totalTextureSize = 0; XMLElement pageElem = pagesElem.GetChild("page"); for (unsigned i = 0; i < pages; ++i) { if (pageElem.IsNull()) { ATOMIC_LOGERROR("Could not find Page element for page: " + String(i)); return 0; } // Assume the font image is in the same directory as the font description file String textureFile = fontPath + pageElem.GetAttribute("file"); // Load texture manually to allow controlling the alpha channel mode SharedPtr fontFile = resourceCache->GetFile(textureFile); SharedPtr fontImage(new Image(context)); if (!fontFile || !fontImage->Load(*fontFile)) { ATOMIC_LOGERROR("Failed to load font image file"); return 0; } SharedPtr texture = LoadFaceTexture(fontImage); if (!texture) return 0; textures_.Push(texture); // Add texture to resource cache texture->SetName(fontFile->GetName()); resourceCache->AddManualResource(texture); totalTextureSize += fontImage->GetWidth() * fontImage->GetHeight() * fontImage->GetComponents(); pageElem = pageElem.GetNext("page"); } XMLElement charsElem = root.GetChild("chars"); int count = charsElem.GetInt("count"); XMLElement charElem = charsElem.GetChild("char"); while (!charElem.IsNull()) { int id = charElem.GetInt("id"); Text3DFontGlyph glyph; glyph.x_ = (short)charElem.GetInt("x"); glyph.y_ = (short)charElem.GetInt("y"); glyph.width_ = glyph.texWidth_ = (short)charElem.GetInt("width"); glyph.height_ = glyph.texHeight_ = (short)charElem.GetInt("height"); glyph.offsetX_ = (short)charElem.GetInt("xoffset"); glyph.offsetY_ = (short)charElem.GetInt("yoffset"); glyph.advanceX_ = (short)charElem.GetInt("xadvance"); glyph.page_ = charElem.GetUInt("page"); glyphMapping_[id] = glyph; charElem = charElem.GetNext("char"); } XMLElement kerningsElem = root.GetChild("kernings"); if (kerningsElem.NotNull()) { XMLElement kerningElem = kerningsElem.GetChild("kerning"); while (!kerningElem.IsNull()) { int first = kerningElem.GetInt("first"); int second = kerningElem.GetInt("second"); unsigned value = (unsigned)((first << 16) + second); kerningMapping_[value] = (short)kerningElem.GetInt("amount"); kerningElem = kerningElem.GetNext("kerning"); } } ATOMIC_LOGDEBUGF("Bitmap font face %s has %d glyphs", GetFileName(font_->GetName()).CString(), count); font_->SetMemoryUse(font_->GetMemoryUse() + totalTextureSize); return true; } bool Text3DBitmap::Load(Text3DFontFace* fontFace, bool usedGlyphs) { if (this == fontFace) return true; if (!usedGlyphs) { glyphMapping_ = fontFace->glyphMapping_; kerningMapping_ = fontFace->kerningMapping_; textures_ = fontFace->textures_; pointSize_ = fontFace->pointSize_; rowHeight_ = fontFace->rowHeight_; return true; } pointSize_ = fontFace->pointSize_; rowHeight_ = fontFace->rowHeight_; unsigned numPages = 1; int maxTextureSize = TEXT3D_DEFAULT_FONT_TEXTURE_MAX_SIZE;// font_->GetSubsystem()->GetMaxFontTextureSize(); AreaAllocator allocator(TEXT3D_FONT_TEXTURE_MIN_SIZE, TEXT3D_FONT_TEXTURE_MIN_SIZE, maxTextureSize, maxTextureSize); for (HashMap::ConstIterator i = fontFace->glyphMapping_.Begin(); i != fontFace->glyphMapping_.End(); ++i) { Text3DFontGlyph fontGlyph = i->second_; if (!fontGlyph.used_) continue; int x, y; if (!allocator.Allocate(fontGlyph.width_ + 1, fontGlyph.height_ + 1, x, y)) { ++numPages; allocator = AreaAllocator(TEXT3D_FONT_TEXTURE_MIN_SIZE, TEXT3D_FONT_TEXTURE_MIN_SIZE, maxTextureSize, maxTextureSize); if (!allocator.Allocate(fontGlyph.width_ + 1, fontGlyph.height_ + 1, x, y)) return false; } fontGlyph.x_ = (short)x; fontGlyph.y_ = (short)y; fontGlyph.page_ = numPages - 1; glyphMapping_[i->first_] = fontGlyph; } // Assume that format is the same for all textures and that bitmap font type may have more than one component unsigned components = ConvertFormatToNumComponents(fontFace->textures_[0]->GetFormat()); // Save the existing textures as image resources Vector > oldImages; for (unsigned i = 0; i < fontFace->textures_.Size(); ++i) oldImages.Push(SaveFaceTexture(fontFace->textures_[i])); Vector > newImages(numPages); for (unsigned i = 0; i < numPages; ++i) { SharedPtr image(new Image(font_->GetContext())); int width = maxTextureSize; int height = maxTextureSize; if (i == numPages - 1) { width = allocator.GetWidth(); height = allocator.GetHeight(); } image->SetSize(width, height, components); memset(image->GetData(), 0, width * height * components); newImages[i] = image; } for (HashMap::Iterator i = glyphMapping_.Begin(); i != glyphMapping_.End(); ++i) { Text3DFontGlyph& newGlyph = i->second_; const Text3DFontGlyph& oldGlyph = fontFace->glyphMapping_[i->first_]; Blit(newImages[newGlyph.page_], newGlyph.x_, newGlyph.y_, newGlyph.width_, newGlyph.height_, oldImages[oldGlyph.page_], oldGlyph.x_, oldGlyph.y_, components); } textures_.Resize(newImages.Size()); for (unsigned i = 0; i < newImages.Size(); ++i) textures_[i] = LoadFaceTexture(newImages[i]); for (HashMap::ConstIterator i = fontFace->kerningMapping_.Begin(); i != fontFace->kerningMapping_.End(); ++i) { unsigned first = (i->first_) >> 16; unsigned second = (i->first_) & 0xffff; if (glyphMapping_.Find(first) != glyphMapping_.End() && glyphMapping_.Find(second) != glyphMapping_.End()) kerningMapping_[i->first_] = i->second_; } return true; } bool Text3DBitmap::Save(Serializer& dest, int pointSize, const String& indentation) { Context* context = font_->GetContext(); SharedPtr xml(new XMLFile(context)); XMLElement rootElem = xml->CreateRoot("font"); // Information XMLElement childElem = rootElem.CreateChild("info"); String fileName = GetFileName(font_->GetName()); childElem.SetAttribute("face", fileName); childElem.SetAttribute("size", String(pointSize)); // Common childElem = rootElem.CreateChild("common"); childElem.SetInt("lineHeight", rowHeight_); unsigned pages = textures_.Size(); childElem.SetUInt("pages", pages); // Construct the path to store the texture String pathName; File* file = dynamic_cast(&dest); if (file) // If serialize to file, use the file's path pathName = GetPath(file->GetName()); else // Otherwise, use the font resource's path pathName = "Data/" + GetPath(font_->GetName()); // Pages childElem = rootElem.CreateChild("pages"); for (unsigned i = 0; i < pages; ++i) { XMLElement pageElem = childElem.CreateChild("page"); pageElem.SetInt("id", i); String texFileName = fileName + "_" + String(i) + ".png"; pageElem.SetAttribute("file", texFileName); // Save the font face texture to image file SaveFaceTexture(textures_[i], pathName + texFileName); } // Chars and kernings XMLElement charsElem = rootElem.CreateChild("chars"); unsigned numGlyphs = glyphMapping_.Size(); charsElem.SetInt("count", numGlyphs); for (HashMap::ConstIterator i = glyphMapping_.Begin(); i != glyphMapping_.End(); ++i) { // Char XMLElement charElem = charsElem.CreateChild("char"); charElem.SetInt("id", i->first_); const Text3DFontGlyph& glyph = i->second_; charElem.SetInt("x", glyph.x_); charElem.SetInt("y", glyph.y_); charElem.SetInt("width", glyph.width_); charElem.SetInt("height", glyph.height_); charElem.SetInt("xoffset", glyph.offsetX_); charElem.SetInt("yoffset", glyph.offsetY_); charElem.SetInt("xadvance", glyph.advanceX_); charElem.SetUInt("page", glyph.page_); } if (!kerningMapping_.Empty()) { XMLElement kerningsElem = rootElem.CreateChild("kernings"); for (HashMap::ConstIterator i = kerningMapping_.Begin(); i != kerningMapping_.End(); ++i) { XMLElement kerningElem = kerningsElem.CreateChild("kerning"); kerningElem.SetInt("first", i->first_ >> 16); kerningElem.SetInt("second", i->first_ & 0xffff); kerningElem.SetInt("amount", i->second_); } } return xml->Save(dest, indentation); } unsigned Text3DBitmap::ConvertFormatToNumComponents(unsigned format) { if (format == Graphics::GetRGBAFormat()) return 4; else if (format == Graphics::GetRGBFormat()) return 3; else if (format == Graphics::GetLuminanceAlphaFormat()) return 2; else return 1; } SharedPtr Text3DBitmap::SaveFaceTexture(Texture2D* texture) { SharedPtr image(new Image(font_->GetContext())); image->SetSize(texture->GetWidth(), texture->GetHeight(), ConvertFormatToNumComponents(texture->GetFormat())); if (!texture->GetData(0, image->GetData())) { ATOMIC_LOGERROR("Could not save texture to image resource"); return SharedPtr(); } return image; } bool Text3DBitmap::SaveFaceTexture(Texture2D* texture, const String& fileName) { SharedPtr image = SaveFaceTexture(texture); return image ? image->SavePNG(fileName) : false; } void Text3DBitmap::Blit(Image* dest, int x, int y, int width, int height, Image* source, int sourceX, int sourceY, int components) { unsigned char* destData = dest->GetData() + (y * dest->GetWidth() + x) * components; unsigned char* sourceData = source->GetData() + (sourceY * source->GetWidth() + sourceX) * components; for (int i = 0; i < height; ++i) { memcpy(destData, sourceData, (size_t)(width * components)); destData += dest->GetWidth() * components; sourceData += source->GetWidth() * components; } } }