diff --git a/Components/Overlay/include/OgreFont.h b/Components/Overlay/include/OgreFont.h index 127465cb944..f10846fba23 100644 --- a/Components/Overlay/include/OgreFont.h +++ b/Components/Overlay/include/OgreFont.h @@ -28,6 +28,7 @@ THE SOFTWARE #define _Font_H__ #include "OgreOverlayPrerequisites.h" +#include "OgrePrerequisites.h" #include "OgreResource.h" #include "OgreCommon.h" #include "OgreSharedPtr.h" @@ -44,6 +45,8 @@ namespace Ogre /** \addtogroup Overlays * @{ */ + class Font; + typedef SharedPtr FontPtr; /// decode UTF8 encoded bytestream to uint32 codepoints _OgreOverlayExport std::vector utftoc32(String str); @@ -124,12 +127,21 @@ namespace Ogre /// Range of code points to generate glyphs for (truetype only) CodePointRangeList mCodePointRangeList; + std::vector mMergeFonts; + /// Internal method for loading from ttf void createTextureFromFont(void); void loadImpl() override; void unloadImpl() override; size_t calculateSize(void) const override { return 0; } // permanent resource is in the texture + + friend class ImGuiOverlay; + DataStreamPtr _getTTFData(); + + void* _prepareFont(void* context, uint32& glyphCount, int32& max_height, int32& max_width); + void _loadGlyphs(void* font, int32 max_height, Image& img, uint32& l, uint32& m); + public: /** Constructor. @@ -287,6 +299,16 @@ namespace Ogre { return mCodePointRangeList; } + + /** Add a font to merge with this one. + + This is useful when you want to use a font for most of the characters, but + fall back to another font for characters not present in the current font. e.g. icons. + */ + void addMergeFont(const FontPtr& font) { mMergeFonts.push_back(font); } + + const std::vector& getMergeFontList() const { return mMergeFonts; } + /** Gets the material generated for this font, as a weak reference. This will only be valid after the Font has been loaded. @@ -343,8 +365,6 @@ namespace Ogre */ void _setMaterial(const MaterialPtr& mat); }; - - typedef SharedPtr FontPtr; /** @} */ /** @} */ } diff --git a/Components/Overlay/src/OgreFont.cpp b/Components/Overlay/src/OgreFont.cpp index 97511dd34a7..ccaa90914e8 100644 --- a/Components/Overlay/src/OgreFont.cpp +++ b/Components/Overlay/src/OgreFont.cpp @@ -328,78 +328,61 @@ namespace Ogre mTexture->load(); } //--------------------------------------------------------------------- - void Font::loadResource(Resource* res) + DataStreamPtr Font::_getTTFData() { - // Locate ttf file, load it pre-buffered into memory by wrapping the - // original DataStream in a MemoryDataStream - DataStreamPtr dataStreamPtr = - ResourceGroupManager::getSingleton().openResource( - mSource, mGroup, this); - MemoryDataStream ttfchunk(dataStreamPtr); + // Locate ttf file, load it pre-buffered into memory by wrapping + // the original DataStream in a MemoryDataStream + return ResourceGroupManager::getSingleton().openResource(mSource, mGroup, this); + } - // If codepoints not supplied, assume ASCII - if (mCodePointRangeList.empty()) - { - mCodePointRangeList.push_back(CodePointRange(32, 126)); - } + void* Font::_prepareFont(void* context, uint32& glyphCount, int32& max_height, int32& max_width) + { float vpScale = OverlayManager::getSingleton().getPixelRatio(); + MemoryDataStream ttfchunk(_getTTFData(), false); #ifdef HAVE_FREETYPE - // ManualResourceLoader implementation - load the texture - FT_Library ftLibrary; - // Init freetype - if( FT_Init_FreeType( &ftLibrary ) ) - OGRE_EXCEPT( Exception::ERR_INTERNAL_ERROR, "Could not init FreeType library!", - "Font::Font"); - + FT_Library ftLibrary = static_cast(context); FT_Face face; // Load font - if( FT_New_Memory_Face( ftLibrary, ttfchunk.getPtr(), (FT_Long)ttfchunk.size() , 0, &face ) ) - OGRE_EXCEPT( Exception::ERR_INTERNAL_ERROR, - "Could not open font face!", "Font::createTextureFromFont" ); - + if (FT_New_Memory_Face(ftLibrary, ttfchunk.getPtr(), (FT_Long)ttfchunk.size(), 0, &face)) + OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, "Could not open font face"); // Convert our point size to freetype 26.6 fixed point format FT_F26Dot6 ftSize = (FT_F26Dot6)(mTtfSize * (1 << 6)); if (FT_Set_Char_Size(face, ftSize, 0, mTtfResolution * vpScale, mTtfResolution * vpScale)) - OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, "Could not set char size!"); - - //FILE *fo_def = stdout; - - FT_Pos max_height = 0, max_width = 0; + OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, "Could not set char size"); // Calculate maximum width, height and bearing - size_t glyphCount = 0; for (const CodePointRange& range : mCodePointRangeList) { - for(CodePoint cp = range.first; cp <= range.second; ++cp, ++glyphCount) + glyphCount += range.second - range.first + 1; + for(CodePoint cp = range.first; cp <= range.second; ++cp) { FT_Load_Char( face, cp, FT_LOAD_RENDER ); - max_height = std::max(2 * face->glyph->bitmap.rows - (face->glyph->metrics.horiBearingY >> 6), max_height); + max_height = std::max(2 * face->glyph->bitmap.rows - (face->glyph->metrics.horiBearingY >> 6), max_height); mTtfMaxBearingY = std::max(int(face->glyph->metrics.horiBearingY >> 6), mTtfMaxBearingY); - max_width = std::max(face->glyph->bitmap.width, max_width); + max_width = std::max(face->glyph->bitmap.width, max_width); } - } + + return face; #else - stbtt_fontinfo font; - stbtt_InitFont(&font, ttfchunk.getPtr(), 0); - // 64 gives the same texture resolution as freetype. - float scale = stbtt_ScaleForPixelHeight(&font, vpScale * mTtfSize * mTtfResolution / 64); + stbtt_fontinfo* font = static_cast(context); + stbtt_InitFont(font, ttfchunk.getPtr(), 0); - int max_width = 0, max_height = 0; - // Calculate maximum width, height and bearing - size_t glyphCount = 0; + // 64 gives the same texture resolution as freetype. + float scale = stbtt_ScaleForPixelHeight(font, vpScale * mTtfSize * mTtfResolution / 64); for (const CodePointRange& range : mCodePointRangeList) { - for(CodePoint cp = range.first; cp <= range.second; ++cp, ++glyphCount) + glyphCount += range.second - range.first + 1; + for(CodePoint cp = range.first; cp <= range.second; ++cp) { - int idx = stbtt_FindGlyphIndex(&font, cp); + int idx = stbtt_FindGlyphIndex(font, cp); if (!idx) // It is actually in the font? continue; TRect r; - stbtt_GetGlyphBitmapBox(&font, idx, scale, scale, &r.left, &r.top, &r.right, &r.bottom); + stbtt_GetGlyphBitmapBox(font, idx, scale, scale, &r.left, &r.top, &r.right, &r.bottom); max_height = std::max(r.height(), max_height); mTtfMaxBearingY = std::max(-r.top, mTtfMaxBearingY); max_width = std::max(r.width(), max_width); @@ -407,35 +390,23 @@ namespace Ogre } max_height *= 1.125; + return font; #endif - uint char_spacer = 1; - - // Now work out how big our texture needs to be - size_t rawSize = (max_width + char_spacer) * (max_height + char_spacer) * glyphCount; - - uint32 tex_side = static_cast(Math::Sqrt((Real)rawSize)); - // Now round up to nearest power of two - uint32 roundUpSize = Bitwise::firstPO2From(tex_side); - - // Would we benefit from using a non-square texture (2X width) - uint32 finalWidth, finalHeight; - if (roundUpSize * roundUpSize * 0.5 >= rawSize) - { - finalHeight = static_cast(roundUpSize * 0.5); - } - else - { - finalHeight = roundUpSize; - } - finalWidth = roundUpSize; + } - Real textureAspect = (Real)finalWidth / (Real)finalHeight; + void Font::_loadGlyphs(void* _face, int32 max_height, Image& img, uint32& l, uint32& m) + { + uint char_spacer = 1; + float finalWidth = img.getWidth(); + float finalHeight = img.getHeight(); + float textureAspect = finalWidth / finalHeight; - Image img(PF_BYTE_LA, finalWidth, finalHeight); - // Reset content (transparent) - img.setTo(ColourValue::ZERO); +#ifdef HAVE_FREETYPE + FT_Face face = static_cast(_face); +#else + stbtt_fontinfo* font = static_cast(_face); +#endif - uint32 l = 0, m = 0; for (const CodePointRange& range : mCodePointRangeList) { for(CodePoint cp = range.first; cp <= range.second; ++cp ) @@ -464,7 +435,7 @@ namespace Ogre FT_Pos y_bearing = mTtfMaxBearingY - (face->glyph->metrics.horiBearingY >> 6); FT_Pos x_bearing = face->glyph->metrics.horiBearingX >> 6; #else - int idx = stbtt_FindGlyphIndex(&font, cp); + int idx = stbtt_FindGlyphIndex(font, cp); if (!idx) { LogManager::getSingleton().logWarning( @@ -473,20 +444,20 @@ namespace Ogre } if(cp == ' ') // should figure out how advance works for stbtt.. - idx = stbtt_FindGlyphIndex(&font, '0'); + idx = stbtt_FindGlyphIndex(font, '0'); TRect r; - stbtt_GetGlyphBitmapBox(&font, idx, scale, scale, &r.left, &r.top, &r.right, &r.bottom); + stbtt_GetGlyphBitmapBox(font, idx, scale, scale, &r.left, &r.top, &r.right, &r.bottom); uint width = r.width(); int y_bearing = mTtfMaxBearingY + r.top; int xoff = 0, yoff = 0; - buffer = stbtt_GetCodepointBitmap(&font, scale, scale, cp, &buffer_pitch, &buffer_h, &xoff, &yoff); + buffer = stbtt_GetCodepointBitmap(font, scale, scale, cp, &buffer_pitch, &buffer_h, &xoff, &yoff); int advance = xoff + width, x_bearing = xoff; // should be multiplied with scale, but still does not seem to do the right thing - // stbtt_GetGlyphHMetrics(&font, cp, &advance, &x_bearing); + // stbtt_GetGlyphHMetrics(font, cp, &advance, &x_bearing); #endif // If at end of row if( finalWidth - 1 < l + width ) @@ -532,11 +503,76 @@ namespace Ogre #ifndef HAVE_FREETYPE if (buffer != NULL) { - STBTT_free(buffer, font.userdata); + STBTT_free(buffer, font->userdata); } #endif } } + } + + void Font::loadResource(Resource* res) + { + // If codepoints not supplied, assume ASCII + if (mCodePointRangeList.empty()) + { + mCodePointRangeList.push_back(CodePointRange(32, 126)); + } + + int32 max_height = 0, max_width = 0; + uint32 glyphCount = 0; +#ifdef HAVE_FREETYPE + // ManualResourceLoader implementation - load the texture + FT_Library ftLibrary; + // Init freetype + if( FT_Init_FreeType( &ftLibrary ) ) + OGRE_EXCEPT( Exception::ERR_INTERNAL_ERROR, "Could not init FreeType library"); + + std::vector faces; + faces.push_back(_prepareFont(ftLibrary, glyphCount, max_height, max_width)); + + for (const auto& font : mMergeFonts) + { + faces.push_back(font->_prepareFont(ftLibrary, glyphCount, max_height, max_width)); + font->mTtfMaxBearingY = mTtfMaxBearingY = std::max(font->mTtfMaxBearingY, mTtfMaxBearingY); + } +#else + stbtt_fontinfo font; + auto face = _prepareFont(&font, glyphCount, max_height, max_width); +#endif + uint char_spacer = 1; + + // Now work out how big our texture needs to be + size_t rawSize = (max_width + char_spacer) * (max_height + char_spacer) * glyphCount; + + uint32 tex_side = static_cast(Math::Sqrt((Real)rawSize)); + // Now round up to nearest power of two + uint32 roundUpSize = Bitwise::firstPO2From(tex_side); + + // Would we benefit from using a non-square texture (2X width) + uint32 finalWidth, finalHeight; + if (roundUpSize * roundUpSize * 0.5 >= rawSize) + { + finalHeight = static_cast(roundUpSize * 0.5); + } + else + { + finalHeight = roundUpSize; + } + finalWidth = roundUpSize; + + Image img(PF_BYTE_LA, finalWidth, finalHeight); + // Reset content (transparent) + img.setTo(ColourValue::ZERO); + + uint32 l = 0, m = 0; + _loadGlyphs(faces[0], max_height, img, l, m); + int j = 1; + for (const auto& font : mMergeFonts) + { + font->_loadGlyphs(faces[j++], max_height, img, l, m); + mCodePointMap.insert(font->mCodePointMap.begin(), font->mCodePointMap.end()); + } + #ifdef HAVE_FREETYPE FT_Done_FreeType(ftLibrary); #endif diff --git a/Components/Overlay/src/OgreImGuiOverlay.cpp b/Components/Overlay/src/OgreImGuiOverlay.cpp index dc9907a0f3c..0d4191b4cd0 100644 --- a/Components/Overlay/src/OgreImGuiOverlay.cpp +++ b/Components/Overlay/src/OgreImGuiOverlay.cpp @@ -123,9 +123,7 @@ ImFont* ImGuiOverlay::addFont(const String& name, const String& group) StringUtil::format("Font '%s' not found in group '%s'", name.c_str(), group.c_str())); OgreAssert(font->getType() == FT_TRUETYPE, "font must be of FT_TRUETYPE"); - DataStreamPtr dataStreamPtr = - ResourceGroupManager::getSingleton().openResource(font->getSource(), font->getGroup()); - MemoryDataStream ttfchunk(dataStreamPtr, false); // transfer ownership to imgui + MemoryDataStream ttfchunk(font->_getTTFData(), false); // transfer ownership to imgui // convert codepoint ranges for imgui CodePointRange cprange; @@ -149,8 +147,33 @@ ImFont* ImGuiOverlay::addFont(const String& name, const String& group) ImFontConfig cfg; strncpy(cfg.Name, name.c_str(), IM_ARRAYSIZE(cfg.Name) - 1); - return io.Fonts->AddFontFromMemoryTTF(ttfchunk.getPtr(), ttfchunk.size(), font->getTrueTypeSize() * vpScale, &cfg, - cprangePtr); + auto* ret = io.Fonts->AddFontFromMemoryTTF(ttfchunk.getPtr(), ttfchunk.size(), font->getTrueTypeSize() * vpScale, + &cfg, cprangePtr); + + cfg.MergeMode = true; + + for(const auto& mergeFont : font->getMergeFontList()) + { + MemoryDataStream mergeTtfchunk(mergeFont->_getTTFData(), false); // transfer ownership to imgui + + CodePointRange mergeCprange; + for (const auto& r : mergeFont->getCodePointRangeList()) + { + mergeCprange.push_back(r.first); + mergeCprange.push_back(r.second); + } + + OgreAssert(!mergeCprange.empty(), "merge font must have codepoint ranges"); + mergeCprange.push_back(0); // terminate + mCodePointRanges.push_back(mergeCprange); + cprangePtr = mCodePointRanges.back().data(); + + cfg.MergeMode = true; + ret = io.Fonts->AddFontFromMemoryTTF(mergeTtfchunk.getPtr(), mergeTtfchunk.size(), + mergeFont->getTrueTypeSize() * vpScale, &cfg, cprangePtr); + } + + return ret; } void ImGuiOverlay::ImGUIRenderable::createFontTexture() diff --git a/Components/Overlay/src/OgreOverlayTranslator.cpp b/Components/Overlay/src/OgreOverlayTranslator.cpp index 04401cee398..489e0f7a0e6 100644 --- a/Components/Overlay/src/OgreOverlayTranslator.cpp +++ b/Components/Overlay/src/OgreOverlayTranslator.cpp @@ -106,6 +106,31 @@ void FontTranslator::parseAttribute(ScriptCompiler* compiler, FontPtr& pFont, pFont->setAntialiasColour(flag); compiler->addError(ScriptCompiler::CE_DEPRECATEDSYMBOL, prop->file, prop->line, attrib); } + else if(attrib == "merge_fonts") + { + if (prop->values.empty()) + { + compiler->addError(ScriptCompiler::CE_INVALIDPARAMETERS, prop->file, prop->line); + return; + } + + for (auto& v : prop->values) + { + if (!getString(v, &val)) + { + compiler->addError(ScriptCompiler::CE_INVALIDPARAMETERS, prop->file, prop->line); + return; + } + + auto mergeFont = FontManager::getSingleton().getByName(val, pFont->getGroup()); + if (!mergeFont) + { + compiler->addError(ScriptCompiler::CE_REFERENCETOANONEXISTINGOBJECT, prop->file, prop->line, val); + return; + } + pFont->addMergeFont(mergeFont); + } + } else if (attrib == "code_points") { if (prop->values.empty()) diff --git a/Docs/src/scripts.md b/Docs/src/scripts.md index e02795f459b..eb3c1f787e7 100644 --- a/Docs/src/scripts.md +++ b/Docs/src/scripts.md @@ -1299,4 +1299,6 @@ Here are the attributes you need to supply: @param code\_points nn-nn \[nn-nn\] .. This directive allows you to specify which unicode code points should be generated as glyphs into the font texture. If you don’t specify this, code points 32-126 will be generated by default which covers the ASCII glyphs. If you use this flag, you should specify a space-separated list of inclusive code point ranges of the form ’start-end’. Numbers must be decimal. +@param merge_font **name [name2]** @copydetails Ogre::Font::addMergeFont + You can also create new fonts at runtime by using the Ogre::FontManager if you wish. diff --git a/Media/packs/SdkTrays.zip b/Media/packs/SdkTrays.zip index 1b55966baae..93d7eb1ee36 100644 Binary files a/Media/packs/SdkTrays.zip and b/Media/packs/SdkTrays.zip differ diff --git a/Samples/Browser/include/SampleBrowser.h b/Samples/Browser/include/SampleBrowser.h index dc76889d060..cb6b243f2cd 100644 --- a/Samples/Browser/include/SampleBrowser.h +++ b/Samples/Browser/include/SampleBrowser.h @@ -401,7 +401,7 @@ namespace OgreBites ImGui::SetNextWindowPos(ImVec2(0, center.y), ImGuiCond_Always, ImVec2(0.f, 0.5f)); else ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); - ImGui::Begin("Configuration", NULL, flags); + ImGui::Begin(u8"\uf013 Configuration", NULL, flags); Ogre::DrawRenderingSettings(mNextRenderer); ImGui::End(); #endif @@ -972,7 +972,7 @@ namespace OgreBites #endif mTitleLabel = mTrayMgr->createLabel(TL_LEFT, "SampleTitle", ""); mDescBox = mTrayMgr->createTextBox(TL_LEFT, "SampleInfo", "Sample Info", infoWidth, 208); - mCategoryMenu = mTrayMgr->createThickSelectMenu(TL_LEFT, "CategoryMenu", "Select Category", infoWidth, 10); + mCategoryMenu = mTrayMgr->createThickSelectMenu(TL_LEFT, "CategoryMenu", u8"\uf013 Select Category", infoWidth, 10); mSampleMenu = mTrayMgr->createThickSelectMenu(TL_LEFT, "SampleMenu", "Select Sample", infoWidth, 10); mSampleSlider = mTrayMgr->createThickSlider(TL_LEFT, "SampleSlider", "Slide Samples", infoWidth, 80, 0, 0, 0);