Skip to content

Commit

Permalink
Overlay: Font - add merge_fonts property
Browse files Browse the repository at this point in the history
  • Loading branch information
paroj committed Nov 10, 2024
1 parent a968b2b commit 2aaf09c
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 82 deletions.
24 changes: 22 additions & 2 deletions Components/Overlay/include/OgreFont.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ THE SOFTWARE
#define _Font_H__

#include "OgreOverlayPrerequisites.h"
#include "OgrePrerequisites.h"
#include "OgreResource.h"
#include "OgreCommon.h"
#include "OgreSharedPtr.h"
Expand All @@ -44,6 +45,8 @@ namespace Ogre
/** \addtogroup Overlays
* @{
*/
class Font;
typedef SharedPtr<Font> FontPtr;

/// decode UTF8 encoded bytestream to uint32 codepoints
_OgreOverlayExport std::vector<uint32> utftoc32(String str);
Expand Down Expand Up @@ -124,12 +127,21 @@ namespace Ogre
/// Range of code points to generate glyphs for (truetype only)
CodePointRangeList mCodePointRangeList;

std::vector<FontPtr> 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.
Expand Down Expand Up @@ -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<FontPtr>& 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.
Expand Down Expand Up @@ -343,8 +365,6 @@ namespace Ogre
*/
void _setMaterial(const MaterialPtr& mat);
};

typedef SharedPtr<Font> FontPtr;
/** @} */
/** @} */
}
Expand Down
171 changes: 97 additions & 74 deletions Components/Overlay/src/OgreFont.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,114 +328,85 @@ 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();
#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<FT_Library>(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<FT_Pos>(2 * face->glyph->bitmap.rows - (face->glyph->metrics.horiBearingY >> 6), max_height);
max_height = std::max<int32>(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<FT_Pos>(face->glyph->bitmap.width, max_width);
max_width = std::max<int32>(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<stbtt_fontinfo*>(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<int> 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);
}
}

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<uint32>(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<uint32>(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<FT_Face>(_face);
#else
stbtt_fontinfo* font = static_cast<stbtt_fontinfo*>(_face);
#endif

uint32 l = 0, m = 0;
for (const CodePointRange& range : mCodePointRangeList)
{
for(CodePoint cp = range.first; cp <= range.second; ++cp )
Expand Down Expand Up @@ -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(
Expand All @@ -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<int> 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 )
Expand Down Expand Up @@ -532,11 +503,63 @@ 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");

auto face = _prepareFont(ftLibrary, glyphCount, max_height, max_width);
#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<uint32>(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<uint32>(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(face, max_height, img, l, m);

#ifdef HAVE_FREETYPE
FT_Done_FreeType(ftLibrary);
#endif
Expand Down
33 changes: 28 additions & 5 deletions Components/Overlay/src/OgreImGuiOverlay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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()
Expand Down
25 changes: 25 additions & 0 deletions Components/Overlay/src/OgreOverlayTranslator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Loading

0 comments on commit 2aaf09c

Please sign in to comment.