Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overlay: Font - add merge_fonts property #3253

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
184 changes: 110 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(), 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<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,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<void*> 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<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(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
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
Loading