Skip to content

Instantly share code, notes, and snippets.

@jamesu
Last active January 22, 2024 13:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesu/136f47fcf20b0b775b3e6e60da241aba to your computer and use it in GitHub Desktop.
Save jamesu/136f47fcf20b0b775b3e6e60da241aba to your computer and use it in GitHub Desktop.
SDF Font Code prototype
//-----------------------------------------------------------------------------
// Copyright (c) 2023 tgemit contributors.
// See AUTHORS file and git repository for contributor information.
//
// SPDX-License-Identifier: MIT
//-----------------------------------------------------------------------------
#pragma once
#include "dgl/gFont.h"
/// Helper class which turns text into a mesh for rendering
class DGLFontMeshBuilder
{
protected:
enum
{
SheetChunkSize = 4096,
COLOR_INDEX_BITMAP_MODULATION=0,
COLOR_INDEX_ANCHOR=1,
COLOR_INDEX_COLOR_TABLE=2,
};
struct DrawChar
{
Point2F pos; // NOTE: non-scaled
U16 charCode;
U16 colorIndex;
};
struct DrawSheet
{
U16 bitmapIndex;
U16 charsUsed;
DrawChar firstChar;
DrawChar* getChars()
{
return &firstChar;
}
};
/// Main font we are drawing with
GFont* mFont;
/// Allocator for DrawSheet
DataChunker mSheetChunker;
/// Sheets to output to
Vector<DrawSheet*> mDrawSheets;
/// Sheets we have outputted
Vector<DrawSheet*> mOutputSheets;
/// Color State
/// {
ColorF mBitmapModulation;
ColorF mAnchorColor;
const ColorI* mColorTable;
U32 mColorTableSize;
U32 mColorIndex;
U32 mSavedColorIndex;
/// }
/// Offset we start at
Point2F mDrawOrigin;
/// Where we are
Point2F mCurrentPoint;
MatrixF mBaseTransform;
public:
DGLFontMeshBuilder() :
mFont(NULL),
mBitmapModulation(1.0f,1.0f,1.0f,1.0f),
mAnchorColor(1.0f,1.0f,1.0f,1.0f),
mColorTable(NULL),
mColorTableSize(0),
mColorIndex(0),
mSavedColorIndex(0),
mDrawOrigin(0,0),
mCurrentPoint(0,0),
mBaseTransform(1)
{
}
RectI getDrawBounds()
{
Point2F minP = Point2F(F32_MAX, F32_MAX);
Point2F maxP = Point2F(F32_MIN, F32_MIN);
for (DrawSheet* outSheet : mOutputSheets)
{
for (S32 i=0; i<outSheet->charsUsed; i += std::max<S32>(outSheet->charsUsed-1, 1))
{
auto info = outSheet->getChars()[i];
const PlatformFont::CharInfo &ci = mFont->getCharInfo(info.charCode);
Point2F ep1 = info.pos;
Point2F ep2 = info.pos + Point2F(ci.width, ci.height);
minP.x = std::min(minP.x, ep1.x);
minP.y = std::min(minP.y, ep1.y);
maxP.x = std::max(maxP.x, ep1.x);
maxP.y = std::max(maxP.y, ep1.y);
minP.x = std::min(minP.x, ep2.x);
minP.y = std::min(minP.y, ep2.y);
maxP.x = std::max(maxP.x, ep2.x);
maxP.y = std::max(maxP.y, ep2.y);
}
}
return RectI(mDrawOrigin.x + minP.x, mDrawOrigin.y + minP.y, maxP.x - minP.x, maxP.y - minP.y);
}
inline void setLastDrawPoint(const Point2F point) { mCurrentPoint = point; }
inline Point2F getLastDrawPoint() const { return mCurrentPoint; }
inline bool getDrawSheet(DrawSheet** outSheet, U32 bitmapIndex)
{
if (bitmapIndex >= mDrawSheets.size())
{
*outSheet = NULL;
return false;
}
DrawSheet* sheet = mDrawSheets[bitmapIndex];
if (!sheet)
{
sheet = allocSheet(bitmapIndex);
mDrawSheets[bitmapIndex] = sheet;
}
*outSheet = sheet;
return true;
}
/// Returns color for index
inline DGLPackedPointU16 getColor(U32 index)
{
switch (index)
{
case COLOR_INDEX_BITMAP_MODULATION:
return mBitmapModulation;
break;
case COLOR_INDEX_ANCHOR:
return mAnchorColor;
break;
default:
U32 realIndex = index-COLOR_INDEX_COLOR_TABLE;
return mColorTable ? mColorTable[realIndex] : ColorI(255,255,255,255);
break;
}
}
/// Allocate a sheet to draw
DrawSheet* allocSheet(U16 bitmapIndex)
{
const U32 allocSize = sizeof(DrawSheet) + (sizeof(DrawChar) * SheetChunkSize);
DrawSheet* sheet = (DrawSheet*)mSheetChunker.alloc(allocSize);
sheet->bitmapIndex = bitmapIndex;
memset(sheet, '\0', allocSize);
mOutputSheets.push_back(sheet);
return sheet;
}
/// Begin drawing a line at origin
void begin(GFont* font, Point2I origin, F32 rot=0.0f, const ColorI* colorTable=NULL, U32 maxColorIndex=0)
{
clear();
mFont = font;
dglGetTextAnchorColor(&mAnchorColor);
dglGetBitmapModulation(&mBitmapModulation);
mColorIndex = COLOR_INDEX_BITMAP_MODULATION;
mSavedColorIndex = COLOR_INDEX_BITMAP_MODULATION;
mColorTable = colorTable;
mColorTableSize = maxColorIndex;
mDrawSheets.setSize(font->mTextureSheets.size());
for (auto itr = mDrawSheets.begin(), itrEnd = mDrawSheets.end(); itr != itrEnd; itr++)
{
*itr = NULL;
}
mDrawOrigin = Point2F(origin.x, origin.y);
mCurrentPoint = Point2F(0,0); // relative to mDrawOrigin
mBaseTransform = MatrixF( EulerF( 0.0, 0.0, mDegToRad( rot ) ) );
}
/// Add text to batch
U32 addText(U32 numChars, const UTF16* chars)
{
const F32 invMetricScale = mFont->getInvMetricScale();
const F32 invTexScale = 1.0f / mFont->getTextureScale();
Point2F pt = mCurrentPoint;
DrawSheet* currentSheet = NULL;
U32 lastBitmapIndex = UINT_MAX;
U32 i = 0;
for (i=0; i<numChars; i++)
{
UTF16 c = chars[i];
// We have to do a little dance here since \t = 0x9, \n = 0xa, and \r = 0xd
if ((c >= 1 && c <= 7) ||
(c >= 11 && c <= 12) ||
(c == 14))
{
// Color code
if (mColorTable)
{
static U8 remap[15] =
{
0x0, // 0 special null terminator
0x0, // 1 ascii start-of-heading??
0x1,
0x2,
0x3,
0x4,
0x5,
0x6,
0x0, // 8 special backspace
0x0, // 9 special tab
0x0, // a special \n
0x7,
0x8,
0x0, // a special \r
0x9
};
U8 remapped = remap[c];
// Ignore if the color is greater than the specified max index:
if ( remapped <= mColorTableSize )
{
mColorIndex = COLOR_INDEX_COLOR_TABLE + remapped;
}
}
continue;
}
// reset color?
if ( c == 15 )
{
mColorIndex = COLOR_INDEX_ANCHOR;
continue;
}
// push color:
if ( c == 16 )
{
mSavedColorIndex = mColorIndex;
continue;
}
// pop color:
if ( c == 17 )
{
mColorIndex = mSavedColorIndex;
continue;
}
// Tab character
if ( c == dT('\t') )
{
const PlatformFont::CharInfo &ci = mFont->getCharInfo( dT(' ') );
pt.x += ci.xIncrement * GFont::TabWidthInSpaces * invMetricScale;
continue;
}
if( !mFont->isValidChar( c ) )
continue;
const PlatformFont::CharInfo &ci = mFont->getCharInfo(c);
const F32 realXOrigin = ci.xOrigin * invMetricScale;
const F32 realYOrigin = ci.yOrigin * invMetricScale;
if (ci.bitmapIndex == -1)
{
pt.x += (realXOrigin * invTexScale * invMetricScale) + (ci.xIncrement * invMetricScale);
continue;
}
if (ci.bitmapIndex != lastBitmapIndex)
{
// Grab sheet
if (!getDrawSheet(&currentSheet, ci.bitmapIndex))
{
lastBitmapIndex = UINT_MAX;
continue;
}
lastBitmapIndex = ci.bitmapIndex;
}
if (ci.width != 0 && ci.height != 0)
{
pt.y = mFont->getBaseline() - (realYOrigin * invTexScale);
F32 charExtra = realXOrigin * invTexScale;
pt.x += charExtra;
DrawChar outChar = {};
outChar.pos = pt;
outChar.colorIndex = mColorIndex;
outChar.charCode = c;
currentSheet->getChars()[currentSheet->charsUsed++] = outChar;
// End sheet if full
if (currentSheet->charsUsed >= SheetChunkSize)
{
mDrawSheets[ci.bitmapIndex] = NULL;
currentSheet = NULL;
lastBitmapIndex = UINT_MAX;
}
pt.x += (ci.xIncrement * invMetricScale) - charExtra;
}
else
{
pt.x += ci.xIncrement * invMetricScale;
}
}
mCurrentPoint = pt;
return i;
}
/// Fill in mesh info
void fillAllocParams(DGLMeshAllocParams& params)
{
U32 vertCount = 0;
U32 indCount = 0;
U32 primCount = 0;
for (DrawSheet* outSheet : mOutputSheets)
{
vertCount += outSheet->charsUsed * 4;
indCount += outSheet->charsUsed * 6;
primCount++;
}
params.primType = DGLPrimitiveTriangle;
params.vertCode = GuiVert::getFmtCode();
params.numVerts = vertCount;
params.numInds = indCount;
params.numPrims = primCount;
}
template<class Y> void fillAlloc(DGLGuiMeshDataRef &outRef, Y* pipeline)
{
const F32 invMetricScale = mFont->getInvMetricScale();
const F32 invTexScale = 1.0f / mFont->getTextureScale();
GuiVert* outVerts = outRef.vertPtr;
U16* outInds = outRef.indexPtr;
DGLPrimitive* outPrims = outRef.primPtr;
U16 curInd = 0;
for (DrawSheet* outSheet : mOutputSheets)
{
TextureObject* lastTexture = mFont->getTextureHandle(outSheet->bitmapIndex);
if (lastTexture == NULL)
continue;
DGLPrimitive prim = {};
GuiVert* startVertPtr = outVerts;
U16* startIndPtr = outInds;
prim.primType = DGLPrimitiveTriangle;
prim.startVert = (U32)(outVerts - outRef.vertPtr);
prim.startInd = (U32)(outInds - outRef.indexPtr);
prim.indexed = 1;
curInd = 0;
Point3F points[4];
for (U32 cn=0; cn < outSheet->charsUsed; cn++)
{
DrawChar& dc = outSheet->getChars()[cn];
const PlatformFont::CharInfo &ci = mFont->getCharInfo(dc.charCode);
Point2F pt = dc.pos;
DGLPackedPointU16 currentColor = getColor(dc.colorIndex);
F32 texLeft = F32(ci.xOffset) / F32(lastTexture->getTextureWidth());
F32 texRight = F32(ci.xOffset + ci.width) / F32(lastTexture->getTextureWidth());
F32 texTop = F32(ci.yOffset) / F32(lastTexture->getTextureHeight());
F32 texBottom = F32(ci.yOffset + ci.height) / F32(lastTexture->getTextureHeight());
F32 screenLeft = pt.x;
F32 screenRight = pt.x + (ci.width * invTexScale);
F32 screenTop = pt.y;
F32 screenBottom = pt.y + (ci.height * invTexScale);
points[0] = Point3F( screenLeft, screenTop, 0.0);
points[1] = Point3F( screenRight, screenTop, 0.0);
points[2] = Point3F( screenLeft, screenBottom, 0.0);
points[3] = Point3F( screenRight, screenBottom, 0.0);
for( int i=0; i<4; i++ )
{
mBaseTransform.mulP( points[i] );
points[i] += Point3F(mDrawOrigin.x, mDrawOrigin.y, 0.0f);
}
// Verts
outVerts[0].setValues(points[0], Point2F(texLeft, texTop), currentColor);
outVerts[1].setValues(points[1], Point2F(texRight, texTop), currentColor);
outVerts[2].setValues(points[2], Point2F(texLeft, texBottom), currentColor);
outVerts[3].setValues(points[3], Point2F(texRight, texBottom), currentColor);
// Indices
outInds[0] = curInd;
outInds[1] = curInd+1;
outInds[2] = curInd+2;
outInds[3] = curInd+2;
outInds[4] = curInd+1;
outInds[5] = curInd+3;
outVerts += 4;
curInd += 4;
outInds += 6;
}
prim.numVerts = (U32)(outVerts - startVertPtr);
prim.numElements = (U32)(outInds - startIndPtr);
*outPrims++ = prim;
outInds += prim.numElements;
outVerts += prim.numVerts;
}
}
/// Outputs to a pipeline mesh outRef
template<typename T> void endToPipelineMesh(T* pipeline, DGLGuiMeshDataRef& outRef)
{
DGLMeshAllocParams allocParams = {};
fillAllocParams(allocParams);
if (pipeline->allocMesh(allocParams, outRef))
{
fillAlloc(outRef, pipeline);
}
}
void clear()
{
mOutputSheets.clear();
mSheetChunker.freeBlocks(true);
mDrawSheets.clear();
}
/// Helper function to draw output sheets to the pipeline with the correct textures
void drawPipelineMesh(DGLMeshIndex outMeshIndex, DGLPipelineState* pipeline)
{
U32 primNum = 0;
for (DrawSheet* outSheet : mOutputSheets)
{
TextureObject* lastTexture = mFont->getTextureHandle(outSheet->bitmapIndex);
if (lastTexture == NULL)
continue;
pipeline->quickSetTexture(lastTexture->getGLTextureName(), DGLSamplerStates::GuiText);
pipeline->drawMesh(outMeshIndex, primNum++);
}
}
static DGLFontMeshBuilder* getInstance();
};
//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
//
// 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 "platform/platform.h"
#include "core/stream.h"
#include "core/frameAllocator.h"
#include "core/findMatch.h"
#include "core/unicode.h"
#include "dgl/gFont.h"
#include "dgl/gBitmap.h"
#include "core/fileStream.h"
#include "dgl/gTexManager.h"
#include "util/safeDelete.h"
#include "platform/profiler.h"
#include "zlib.h"
#include <algorithm>
S32 GFont::smSheetIdCount = 0;
const U32 GFont::csm_fileVersion = 3;
U32 GFont::BaseTextureSheetSize = 256;
ConsoleFunction(populateFontCacheString, void, 4, 4, "(faceName, size, string) "
"Populate the font cache for the specified font with characters from the specified string."
"@param faceName The font's name\n"
"@param size The size of the font.\n"
"@param string The string to use to fill font cache\n"
"@return No return value.")
{
Resource<GFont> f = GFont::create(argv[1], dAtoi(argv[2]), Con::getVariable("$GUI::fontCacheDirectory"));
if(f.isNull())
{
Con::errorf("populateFontCacheString - could not load font '%s %d'!", argv[1], dAtoi(argv[2]));
return;
}
if(!f->hasPlatformFont())
{
Con::errorf("populateFontCacheString - font '%s %d' has no platform font! Cannot generate more characters.", argv[1], dAtoi(argv[2]));
return;
}
// This has the side effect of generating character info, including the bitmaps.
f->getStrWidthPrecise(argv[3]);
}
ConsoleFunction(populateFontCacheRange, void, 5, 5, "(faceName, size, rangeStart, rangeEnd) - "
"Populate the font cache for the specified font with Unicode code points in the specified range. "
"Note we only support BMP-0, so code points range from 0 to 65535."
"@param faceName The name of the font\n"
"@param size The size of the font.\n"
"@param rangeStart The initial Unicode point\n"
"@param rangeEnd The final Unicode point in range\n"
"@return No return value")
{
Resource<GFont> f = GFont::create(argv[1], dAtoi(argv[2]), Con::getVariable("$GUI::fontCacheDirectory"));
if(f.isNull())
{
Con::errorf("populateFontCacheRange - could not load font '%s %d'!", argv[1], dAtoi(argv[2]));
return;
}
U32 rangeStart = dAtoi(argv[3]);
U32 rangeEnd = dAtoi(argv[4]);
if(rangeStart > rangeEnd)
{
Con::errorf("populateFontCacheRange - range start is after end!");
return;
}
if(!f->hasPlatformFont())
{
Con::errorf("populateFontCacheRange - font '%s %d' has no platform font! Cannot generate more characters.", argv[1], dAtoi(argv[2]));
return;
}
// This has the side effect of generating character info, including the bitmaps.
for(U32 i=rangeStart; i<rangeEnd; i++)
{
if(f->isValidChar(i))
f->getCharWidth(i);
else
Con::warnf("populateFontCacheRange - skipping invalid char 0x%x", i);
}
// All done!
}
ConsoleFunction(dumpFontCacheStatus, void, 1, 1, "() Dump a full description "
"of all cached fonts, along with info on the codepoints each contains.\n"
"@return No return value")
{
FindMatch match("*.uft", 4096);
ResourceManager->findMatches(&match);
Con::printf("--------------------------------------------------------------------------");
Con::printf(" Font Cache Usage Report (%d fonts found)", match.numMatches());
for (U32 i = 0; i < (U32)match.numMatches(); i++)
{
char *curMatch = match.matchList[i];
Resource<GFont> font = ResourceManager->load(curMatch);
// Deal with inexplicably missing or failed to load fonts.
if (font.isNull())
{
Con::errorf(" o Couldn't load font : %s", curMatch);
continue;
}
// Ok, dump info!
font->dumpInfo();
}
}
ConsoleFunction(writeFontCache, void, 1, 1, "() force all cached fonts to"
"serialize themselves to the cache."
"@return No return value")
{
FindMatch match("*.uft", 4096);
ResourceManager->findMatches(&match);
Con::printf("--------------------------------------------------------------------------");
Con::printf(" Writing font cache to disk (%d fonts found)", match.numMatches());
for (U32 i = 0; i < (U32)match.numMatches(); i++)
{
char *curMatch = match.matchList[i];
Resource<GFont> font = ResourceManager->load(curMatch);
// Deal with inexplicably missing or failed to load fonts.
if (font.isNull())
{
Con::errorf(" o Couldn't find font : %s", curMatch);
continue;
}
// Ok, dump info!
FileStream stream;
if(ResourceManager->openFileForWrite(stream, curMatch))
{
Con::printf(" o Writing '%s' to disk...", curMatch);
font->write(stream);
stream.close();
}
else
{
Con::errorf(" o Could not open '%s' for write!", curMatch);
}
}
}
ConsoleFunction(populateAllFontCacheString, void, 2, 2, "(string inString) "
"Populate the font cache for all fonts with characters from the specified string.\n"
"@param inString The string to use to set the font caches\n"
"@return No return value.")
{
FindMatch match("*.uft", 4096);
ResourceManager->findMatches(&match);
Con::printf("Populating font cache with string '%s' (%d fonts found)", argv[1], match.numMatches());
for (U32 i = 0; i < (U32)match.numMatches(); i++)
{
char *curMatch = match.matchList[i];
Resource<GFont> font = ResourceManager->load(curMatch);
// Deal with inexplicably missing or failed to load fonts.
if (font.isNull())
{
Con::errorf(" o Couldn't load font : %s", curMatch);
continue;
}
if(!font->hasPlatformFont())
{
Con::errorf("populateAllFontCacheString - font '%s' has no platform font! Cannot generate more characters.", curMatch);
continue;
}
// This has the side effect of generating character info, including the bitmaps.
font->getStrWidthPrecise(argv[1]);
}
}
ConsoleFunction(populateAllFontCacheRange, void, 3, 3, "(rangeStart, rangeEnd) "
"Populate the font cache for all fonts with Unicode code points in the specified range. "
"Note we only support BMP-0, so code points range from 0 to 65535.\n"
"@param rangeStart, rangeEnd The range of the unicode points to populate caches with\n"
"@return No return value")
{
U32 rangeStart = dAtoi(argv[1]);
U32 rangeEnd = dAtoi(argv[2]);
if(rangeStart > rangeEnd)
{
Con::errorf("populateAllFontCacheRange - range start is after end!");
return;
}
FindMatch match("*.uft", 4096);
ResourceManager->findMatches(&match);
Con::printf("Populating font cache with range 0x%x to 0x%x (%d fonts found)", rangeStart, rangeEnd, match.numMatches());
for (U32 i = 0; i < (U32)match.numMatches(); i++)
{
char *curMatch = match.matchList[i];
Resource<GFont> font = ResourceManager->load(curMatch);
// Deal with inexplicably missing or failed to load fonts.
if (font.isNull())
{
Con::errorf(" o Couldn't load font : %s", curMatch);
continue;
}
if(!font->hasPlatformFont())
{
Con::errorf("populateAllFontCacheRange - font '%s' has no platform font! Cannot generate more characters.", curMatch);
continue;
}
// This has the side effect of generating character info, including the bitmaps.
Con::printf(" o Populating font '%s'", curMatch);
for(U32 i=rangeStart; i<rangeEnd; i++)
{
if(font->isValidChar(i))
font->getCharWidth(i);
else
Con::warnf("populateAllFontCacheRange - skipping invalid char 0x%x", i);
}
}
// All done!
}
ConsoleFunction(exportCachedFont, void, 6, 6, "(fontName, size, fileName, padding, kerning) - "
"Export specified font to the specified filename as a PNG. The "
"image can then be processed in Photoshop or another tool and "
"reimported using importCachedFont. Characters in the font are"
"exported as one long strip.\n"
"@param fontName The name of the font to export.\n"
"@param size The size of the font\n"
"@param fileName The export file name.\n"
"@param padding Desired padding settings.\n"
"@param kerning Kerning settings (space between elements)\n"
"@return No return value.")
{
// Read in some params.
const char *fileName = argv[3];
S32 padding = dAtoi(argv[4]);
S32 kerning = dAtoi(argv[5]);
// Tell the font to export itself.
Resource<GFont> f = GFont::create(argv[1], dAtoi(argv[2]), Con::getVariable("$GUI::fontCacheDirectory"));
if(f.isNull())
{
Con::errorf("populateFontCacheString - could not load font '%s %d'!", argv[1], dAtoi(argv[2]));
return;
}
f->exportStrip(fileName, padding, kerning);
}
ConsoleFunction(importCachedFont, void, 6, 6, "(fontName, size, fileName, padding, kerning) "
"Import an image strip from exportCachedFont. Call with the "
"same parameters you called exportCachedFont."
"@param fontName The name of the font to import.\n"
"@param size The size of the font\n"
"@param fileName The imported file name.\n"
"@param padding Desired padding settings.\n"
"@param kerning Kerning settings (space between elements)\n"
"@return No return value.")
{
// Read in some params.
const char *fileName = argv[3];
S32 padding = dAtoi(argv[4]);
S32 kerning = dAtoi(argv[5]);
// Tell the font to import itself.
Resource<GFont> f = GFont::create(argv[1], dAtoi(argv[2]), Con::getVariable("$GUI::fontCacheDirectory"));
if(f.isNull())
{
Con::errorf("populateFontCacheString - could not load font '%s %d'!", argv[1], dAtoi(argv[2]));
return;
}
f->importStrip(fileName, padding, kerning);
}
ConsoleFunction(duplicateCachedFont, void, 4, 4, "(oldFontName, oldFontSize, newFontName) "
"Copy the specified old font to a new name. The new copy will not have a "
"platform font backing it, and so will never have characters added to it. "
"But this is useful for making copies of fonts to add postprocessing effects "
"to via exportCachedFont.\n"
"@param oldFontName The original font.\n"
"@param oldFontSize The original font's size property.\n"
"@param newFontName The name to set the copy to.\n"
"@return No return value.")
{
char newFontFile[256];
GFont::getFontCacheFilename(argv[3], dAtoi(argv[2]), 256, newFontFile);
// Load the original font.
Resource<GFont> font = GFont::create(argv[1], dAtoi(argv[2]), Con::getVariable("$GUI::fontCacheDirectory"));
// Deal with inexplicably missing or failed to load fonts.
if (font.isNull())
{
Con::errorf(" o Couldn't find font : %s", newFontFile);
return;
}
// Ok, dump info!
FileStream stream;
if(ResourceManager->openFileForWrite(stream, newFontFile))
{
Con::printf(" o Writing duplicate font '%s' to disk...", newFontFile);
font->write(stream);
stream.close();
}
else
{
Con::errorf(" o Could not open '%s' for write!", newFontFile);
}
}
static PlatformFont* createSafePlatformFont(const char *name, U32 size, U32 charset = TGE_ANSI_CHARSET)
{
PlatformFont *platFont = createPlatformFont(name, size, charset);
if (platFont == NULL)
{
Con::errorf("Loading platform font failed, trying font fallbacks...");
// Couldn't load the requested font. This probably will be common
// since many unix boxes don't have arial or lucida console installed.
// Attempt to map the font name into a font we're pretty sure exist
// Lucida Console is a common code & console font on windows, and
// Monaco is the recommended code & console font on mac.
// this is the name of the final fallback font.
#ifdef TORQUE_OS_LINUX
const char* fallback = "Arial";
#else
const char* fallback = "Helvetica";
#endif
if(dStricmp(name, fallback) == 0)
{
Con::errorf("Font fallback utterly failed.");
return NULL;
}
else if (dStricmp(name, "arial") == 0)
fallback = "Helvetica";
else if (dStricmp(name, "lucida console") == 0)
fallback = "Monaco";
else if (dStricmp(name, "monaco") == 0)
fallback = "Courier";
platFont = createSafePlatformFont(fallback, size, charset);
}
return platFont;
}
ResourceInstance* constructNewFont(Stream& stream)
{
GFont *ret = new GFont;
if(!ret->read(stream))
{
SAFE_DELETE(ret);
}
if(ret)
{
ret->mPlatformFont = createSafePlatformFont(ret->mFaceName, ret->mSize, ret->mCharSet);
}
return ret;
}
void GFont::getFontCacheFilename(const char *faceName, U32 size, U32 buffLen, char *outBuff)
{
dSprintf(outBuff, buffLen, "%s/%s %d (%s).uft", Con::getVariable("$GUI::fontCacheDirectory"), faceName, size, getFontCharSetName(0));
}
Resource<GFont> GFont::create(const char *faceName, U32 size, const char *cacheDirectory, U32 charset /* = TGE_ANSI_CHARSET */)
{
char buf[256];
dSprintf(buf, sizeof(buf), "%s/%s %d (%s).uft", cacheDirectory, faceName, size, getFontCharSetName(charset));
Resource<GFont> ret = ResourceManager->load(buf);
if(bool(ret))
{
ret->mGFTFile = StringTable->insert(buf);
return ret;
}
PlatformFont *platFont = createSafePlatformFont(faceName, size, charset);
AssertFatal(platFont, "platFont is null");
GFont *resFont = new GFont;
resFont->mPlatformFont = platFont;
resFont->addSheet();
resFont->mGFTFile = StringTable->insert(buf);
resFont->mFaceName = StringTable->insert(faceName);
resFont->mSize = size;
resFont->mCharSet = charset;
resFont->mHeight = platFont->getFontHeight();
resFont->mBaseline = platFont->getFontBaseLine();
resFont->mAscent = platFont->getFontBaseLine();
resFont->mDescent = platFont->getFontHeight() - platFont->getFontBaseLine();
resFont->mTexLineHeight = platFont->getTexLineHeight();
resFont->mTextureScale = platFont->getTexScale();
resFont->mMetricScale = platFont->getMetricScale();
resFont->mSDF = platFont->isSDF();
ResourceManager->add(buf, resFont, false);
return ResourceManager->load(buf);
}
//-------------------------------------------------------------------------
GFont::GFont()
{
VECTOR_SET_ASSOCIATION(mCharInfoList);
VECTOR_SET_ASSOCIATION(mTextureSheets);
for (U32 i = 0; i < (sizeof(mRemapTable) / sizeof(S32)); i++)
mRemapTable[i] = -1;
mCurX = mCurY = mCurSheet = -1;
mPlatformFont = NULL;
mGFTFile = NULL;
mFaceName = NULL;
mSize = 0;
mCharSet = 0;
mNeedSave = false;
mSDF = false;
mTexLineHeight = 0;
mTextureScale = 1.0f;
mMetricScale = 1.0f;
mMutex = Mutex::createMutex();
}
GFont::~GFont()
{
// Need to stop this for now!
mNeedSave = false;
if(mNeedSave)
{
FileStream stream;
if(ResourceManager->openFileForWrite(stream, mGFTFile))
{
write(stream);
stream.close();
}
}
S32 i;
for(i = 0;i < mCharInfoList.size();i++)
{
SAFE_DELETE_ARRAY(mCharInfoList[i].bitmapData);
}
//Luma: decrement reference of the texture handles too
for(i=0;i<mTextureSheets.size();i++)
{
mTextureSheets[i] = 0;
}
SAFE_DELETE(mPlatformFont);
Mutex::destroyMutex(mMutex);
}
void GFont::dumpInfo()
{
// Number and extent of mapped characters?
U32 mapCount = 0, mapBegin=0xFFFF, mapEnd=0;
for(U32 i=0; i<0x10000; i++)
{
if(mRemapTable[i] != -1)
{
mapCount++;
if(i<mapBegin) mapBegin = i;
if(i>mapEnd) mapEnd = i;
}
}
// Let's write out all the info we can on this font.
Con::printf(" '%s' %dpt", mFaceName, mSize);
Con::printf(" - %d texture sheets, %d mapped characters.", mTextureSheets.size(), mapCount);
if(mapCount)
Con::printf(" - Codepoints range from 0x%x to 0x%x.", mapBegin, mapEnd);
else
Con::printf(" - No mapped codepoints.", mapBegin, mapEnd);
Con::printf(" - Platform font is %s.", (mPlatformFont ? "present" : "not present") );
}
//////////////////////////////////////////////////////////////////////////
bool GFont::loadCharInfo(const UTF16 ch)
{
if(mRemapTable[ch] != -1)
return true; // Not really an error
if(mPlatformFont && mPlatformFont->isValidChar(ch))
{
Mutex::lockMutex(mMutex); // the CharInfo returned by mPlatformFont is static data, must protect from changes.
PlatformFont::CharInfo &ci = mPlatformFont->getCharInfo(ch);
if(ci.bitmapData)
addBitmap(ci);
mCharInfoList.push_back(ci);
mRemapTable[ch] = mCharInfoList.size() - 1;
//don't save UFTs on the iPhone
#ifndef TORQUE_OS_IOS
mNeedSave = true;
#endif
Mutex::unlockMutex(mMutex);
return true;
}
return false;
}
void GFont::addBitmap(PlatformFont::CharInfo &charInfo)
{
U32 platformHeight = std::max(mPlatformFont ? mPlatformFont->getTexLineHeight() : 0, mTexLineHeight);
const U32 padding = mSDF ? 2 : 1; // jamesu - best to have padding here in all cases I think
U32 nextCurX = U32(mCurX + charInfo.width + padding); /*7) & ~0x3;*/
U32 nextCurY = U32(mCurY + platformHeight + padding); // + 7) & ~0x3;
if (charInfo.height > mTexLineHeight)
{
U32 delta = charInfo.height - mTexLineHeight;
mTexLineHeight = charInfo.height;
Con::warnf("Character for font %s %u exceeds texture height by %u pixels, extending.", mFaceName, mSize, delta);
}
// These are here for postmortem debugging.
bool routeA = false, routeB = false;
const U32 RealTextureSheetSize = mSDF ? 2*BaseTextureSheetSize : BaseTextureSheetSize;
if(mCurSheet == -1 || nextCurY >= RealTextureSheetSize)
{
routeA = true;
addSheet();
// Recalc our nexts.
nextCurX = U32(mCurX + charInfo.width + padding); // + 7) & ~0x3;
nextCurY = U32(mCurY + platformHeight + padding); // + 7) & ~0x3;
}
if( nextCurX >= RealTextureSheetSize)
{
routeB = true;
mCurX = 0;
mCurY = nextCurY;
mCurX = 0;
// Recalc our nexts.
nextCurX = U32(mCurX + charInfo.width + padding); // + 7) & ~0x3;
nextCurY = U32(mCurY + platformHeight + padding); // + 7) & ~0x3;
}
// Check the Y once more - sometimes we advance to a new row and run off
// the end.
if(nextCurY >= RealTextureSheetSize)
{
routeA = true;
addSheet();
// Recalc our nexts.
nextCurX = U32(mCurX + charInfo.width + padding); // + 7) & ~0x3;
nextCurY = U32(mCurY + platformHeight + padding); // + 7) & ~0x3;
}
charInfo.bitmapIndex = mCurSheet;
charInfo.xOffset = mCurX;
charInfo.yOffset = mCurY;
mCurX = nextCurX;
GBitmap *bmp = mTextureSheets[mCurSheet].getBitmap();
AssertFatal(bmp->getFormat() == GBitmap::Alpha, "GFont::addBitmap - cannot added characters to non-greyscale textures!");
for(S32 y = 0;y < charInfo.height;y++)
for(S32 x = 0;x < charInfo.width;x++)
*bmp->getAddress(x + charInfo.xOffset, y + charInfo.yOffset) = charInfo.bitmapData[y * charInfo.width + x];
mTextureSheets[mCurSheet].refresh();
}
void GFont::addSheet()
{
char buf[30];
dSprintf(buf, sizeof(buf), "newfont_%d", smSheetIdCount++);
const U32 RealTextureSheetSize = mSDF ? 2*BaseTextureSheetSize : BaseTextureSheetSize;
GBitmap *bitmap = new GBitmap(RealTextureSheetSize, RealTextureSheetSize, false, GBitmap::Alpha);
// Set everything to transparent.
U8 *bits = bitmap->getWritableBits();
dMemset(bits, 0, RealTextureSheetSize*RealTextureSheetSize);
TextureHandle handle = TextureHandle(buf, bitmap, BitmapKeepTexture);
handle.setFilterNearest();
mTextureSheets.increment();
constructInPlace(&mTextureSheets.last());
mTextureSheets.last() = handle;
mCurX = 0;
mCurY = 0;
mCurSheet = mTextureSheets.size() - 1;
}
//////////////////////////////////////////////////////////////////////////
const PlatformFont::CharInfo &GFont::getCharInfo(const UTF16 in_charIndex)
{
PROFILE_START(NewFontGetCharInfo);
AssertFatal(in_charIndex, "GFont::getCharInfo - can't get info for char 0!");
if(mRemapTable[in_charIndex] == -1)
{
loadCharInfo(in_charIndex);
}
AssertFatal(mRemapTable[in_charIndex] != -1, "No remap info for this character");
PROFILE_END();
// if we still have no character info, return the default char info.
if(mRemapTable[in_charIndex] == -1)
return getDefaultCharInfo();
else
return mCharInfoList[mRemapTable[in_charIndex]];
}
const PlatformFont::CharInfo &GFont::getDefaultCharInfo()
{
static PlatformFont::CharInfo c;
// c is initialized by the CharInfo default constructor.
return c;
}
//////////////////////////////////////////////////////////////////////////
U32 GFont::getStrWidth(const UTF8* in_pString)
{
AssertFatal(in_pString != NULL, "GFont::getStrWidth: String is NULL, width is undefined");
// If we ain't running debug...
if (in_pString == NULL || *in_pString == '\0')
return 0;
return getStrNWidth(in_pString, dStrlen(in_pString));
}
U32 GFont::getStrWidthPrecise(const UTF8* in_pString)
{
AssertFatal(in_pString != NULL, "GFont::getStrWidth: String is NULL, height is undefined");
// If we ain't running debug...
if (in_pString == NULL)
return 0;
return getStrNWidthPrecise(in_pString, dStrlen(in_pString));
}
//////////////////////////////////////////////////////////////////////////
U32 GFont::getStrNWidth(const UTF8 *str, U32 n)
{
// UTF8 conversion is expensive. Avoid converting in a tight loop.
FrameTemp<UTF16> str16(n + 1);
convertUTF8toUTF16(str, str16, n+1);
return getStrNWidth(str16, dStrlen(str16));
}
U32 GFont::getStrNWidth(const UTF16 *str, U32 n)
{
AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL");
if (str == NULL || str[0] == '\0' || n == 0)
return 0;
F32 totWidth = 0;
UTF16 curChar;
U32 charCount;
for(charCount = 0; charCount < n; charCount++)
{
curChar = str[charCount];
if(curChar == '\0')
break;
if(isValidChar(curChar))
{
const PlatformFont::CharInfo& rChar = getCharInfo(curChar);
totWidth += rChar.xIncrement;
}
else if (curChar == dT('\t'))
{
const PlatformFont::CharInfo& rChar = getCharInfo(dT(' '));
totWidth += rChar.xIncrement * TabWidthInSpaces;
}
}
totWidth *= getInvMetricScale();
return (U32)mCeil(totWidth);
}
U32 GFont::getStrNWidthPrecise(const UTF8 *str, U32 n)
{
FrameTemp<UTF16> str16(n + 1);
convertUTF8toUTF16(str, str16, n+1);
return getStrNWidthPrecise(str16, n);
}
U32 GFont::getStrNWidthPrecise(const UTF16 *str, U32 n)
{
AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL");
if (str == NULL || str[0] == '\0' || n == 0)
return(0);
F32 totWidth = 0;
UTF16 curChar;
S32 charCount = 0;
const F32 invTexScale = 1.0f / getTextureScale();
for(charCount = 0; charCount < n; charCount++)
{
curChar = str[charCount];
if(curChar == '\0')
{
n = charCount;
break;
}
if(isValidChar(curChar))
{
const PlatformFont::CharInfo& rChar = getCharInfo(curChar);
totWidth += rChar.xIncrement;
}
else if (curChar == dT('\t'))
{
const PlatformFont::CharInfo& rChar = getCharInfo(dT(' '));
totWidth += rChar.xIncrement * TabWidthInSpaces;
}
}
// Add in width after last char
UTF16 endChar = str[getMax(0, ((S32)n)-1)];
if (isValidChar(endChar))
{
const PlatformFont::CharInfo& rChar = getCharInfo(endChar);
U32 realMetricWidth = mCeil((F32)rChar.width * invTexScale * getMetricScale());
if (realMetricWidth > rChar.xIncrement)
totWidth += (realMetricWidth - rChar.xIncrement);
}
totWidth *= getInvMetricScale();
return (U32)mCeil(totWidth);
}
U32 GFont::getBreakPos(const UTF16 *str16, U32 slen, U32 width, bool breakOnWhitespace)
{
// Some early out cases.
if(slen==0)
return 0;
U32 ret = 0;
U32 lastws = 0;
UTF16 c;
U32 charCount = 0;
const F32 metricScale = getMetricScale();
const F32 invMetricScale = getInvMetricScale();
U32 scaledWidth = width * metricScale;
for( charCount=0; charCount < slen; charCount++)
{
c = str16[charCount];
if(c == '\0')
break;
if(c == dT('\t'))
c = dT(' ');
if(!isValidChar(c))
{
ret++;
continue;
}
if(c == dT(' '))
lastws = ret+1;
const PlatformFont::CharInfo& rChar = getCharInfo(c);
if(rChar.width > scaledWidth || rChar.xIncrement > (S32)scaledWidth)
{
if(lastws && breakOnWhitespace)
return lastws;
return ret;
}
scaledWidth -= rChar.xIncrement;
ret++;
}
return ret;
}
void GFont::wrapString(const UTF8 *txt, U32 lineWidth, Vector<U32> &startLineOffset, Vector<U32> &lineLen)
{
Con::errorf("GFont::wrapString(): Not yet converted to be UTF-8 safe");
startLineOffset.clear();
lineLen.clear();
if (!txt || !txt[0] || lineWidth < getCharWidth('W')) //make sure the line width is greater then a single character
return;
U32 len = dStrlen(txt);
U32 startLine;
U32 scaledLineWidth = lineWidth * getMetricScale();
for (U32 i = 0; i < len;)
{
startLine = i;
startLineOffset.push_back(startLine);
// loop until the string is too large
bool needsNewLine = false;
U32 lineStrWidth = 0;
for (; i < len; i++)
{
if(isValidChar(txt[i]))
{
lineStrWidth += getCharInfo(txt[i]).xIncrement;
if ( txt[i] == '\n' || lineStrWidth > scaledLineWidth )
{
needsNewLine = true;
break;
}
}
}
if (!needsNewLine)
{
// we are done!
lineLen.push_back(i - startLine);
return;
}
// now determine where to put the newline
// else we need to backtrack until we find a either space character
// or \\ character to break up the line.
S32 j;
for (j = i - 1; j >= (S32)startLine; j--)
{
if (dIsspace(txt[j]))
break;
}
if (j < (S32)startLine)
{
// the line consists of a single word!
// So, just break up the word
j = i - 1;
}
lineLen.push_back(j - startLine);
i = j;
// now we need to increment through any space characters at the
// beginning of the next line
for (i++; i < len; i++)
{
if (!dIsspace(txt[i]) || txt[i] == '\n')
break;
}
}
}
//////////////////////////////////////////////////////////////////////////
bool GFont::read(Stream& io_rStream)
{
// Handle versioning
U32 version;
io_rStream.read(&version);
if((version & 0xFF) != csm_fileVersion)
return false;
mSDF = (version & GFont::SDFFlag) != 0;
char buf[256];
io_rStream.readString(buf);
mFaceName = StringTable->insert(buf);
io_rStream.read(&mSize);
io_rStream.read(&mCharSet);
io_rStream.read(&mHeight);
io_rStream.read(&mBaseline);
io_rStream.read(&mAscent);
io_rStream.read(&mDescent);
if ((version & GFont::VariantHeightFlag) != 0)
{
io_rStream.read(&mTexLineHeight);
}
else
{
mTexLineHeight = mHeight;
}
if ((version & GFont::CustomScaleFlag) != 0)
{
io_rStream.read(&mTextureScale);
io_rStream.read(&mMetricScale);
}
else
{
mTextureScale = 1.0f;
mMetricScale = 1.0f;
}
U32 size = 0;
io_rStream.read(&size);
mCharInfoList.setSize(size);
U32 i;
for(i = 0; i < size; i++)
{
PlatformFont::CharInfo *ci = &mCharInfoList[i];
io_rStream.read(&ci->bitmapIndex);
io_rStream.read(&ci->xOffset);
io_rStream.read(&ci->yOffset);
io_rStream.read(&ci->width);
io_rStream.read(&ci->height);
io_rStream.read(&ci->xOrigin);
io_rStream.read(&ci->yOrigin);
io_rStream.read(&ci->xIncrement);
ci->bitmapData = NULL;
}
U32 numSheets = 0;
io_rStream.read(&numSheets);
for(i = 0; i < numSheets; i++)
{
GBitmap *bmp = new GBitmap;
if(!bmp->readPNG(io_rStream))
{
delete bmp;
return false;
}
char buf[30];
dSprintf(buf, sizeof(buf), "font_%d", smSheetIdCount++);
mTextureSheets.increment();
constructInPlace(&mTextureSheets.last());
mTextureSheets.last() = TextureHandle(buf, bmp, BitmapKeepTexture);
mTextureSheets.last().setFilterNearest();
}
// Read last position info
io_rStream.read(&mCurX);
io_rStream.read(&mCurY);
io_rStream.read(&mCurSheet);
// Read the remap table.
U32 minGlyph, maxGlyph;
io_rStream.read(&minGlyph);
io_rStream.read(&maxGlyph);
if(maxGlyph >= minGlyph)
{
// Length of buffer..
U32 buffLen;
io_rStream.read(&buffLen);
// Read the buffer.
FrameTemp<S32> inBuff(buffLen);
io_rStream.read(buffLen, inBuff);
// Decompress.
uLongf destLen = (maxGlyph-minGlyph+1)*sizeof(S32);
uncompress((Bytef*)&mRemapTable[minGlyph], &destLen, (Bytef*)(S32*)inBuff, buffLen);
AssertISV(destLen == (maxGlyph-minGlyph+1)*sizeof(S32), "GFont::read - invalid remap table data!");
// Make sure we've got the right endianness.
for(i = minGlyph; i <= maxGlyph; i++) {
mRemapTable[i] = convertBEndianToHost(mRemapTable[i]);
//if( mRemapTable[i] == -1 ) {
// Con::errorf( "bogus mRemapTable[i] value in %s %i", mFaceName, mSize );
//}
}
}
return (io_rStream.getStatus() == Stream::Ok);
}
bool GFont::write(Stream& stream)
{
// Handle versioning
U32 extraFlags = 0;
if (mSDF) extraFlags |= GFont::SDFFlag;
if (mTexLineHeight != mHeight) extraFlags |= GFont::VariantHeightFlag;
if (mTextureScale != 1.0f || mMetricScale != 1.0f) extraFlags |= GFont::CustomScaleFlag;
stream.write(csm_fileVersion | extraFlags);
// Write font info
stream.writeString(mFaceName);
stream.write(mSize);
stream.write(mCharSet);
stream.write(mHeight);
stream.write(mBaseline);
stream.write(mAscent);
stream.write(mDescent);
if ((extraFlags & GFont::VariantHeightFlag) != 0)
{
stream.write(mTexLineHeight);
}
if ((extraFlags & GFont::CustomScaleFlag) != 0)
{
stream.write(&mTextureScale);
stream.write(&mMetricScale);
}
else
{
mTextureScale = 1.0f;
mMetricScale = 1.0f;
}
// Get the min/max we have values for, and only write that range out.
S32 minGlyph = S32_MAX, maxGlyph = 0;
S32 i;
for(i = 0; i < 65536; i++)
{
if(mRemapTable[i] != -1)
{
if(i > maxGlyph) maxGlyph = i;
if(i < minGlyph) minGlyph = i;
}
}
//-Mat make sure all our character info is good before writing it
for(i = minGlyph; i <= maxGlyph; i++) {
if( mRemapTable[i] == -1 ) {
//-Mat get info and try this again
getCharInfo(i);
if( mRemapTable[i] == -1 ) {
Con::errorf( "GFont::write() couldn't get character info for char %i", i);
}
}
}
// Write char info list
stream.write(U32(mCharInfoList.size()));
for(i = 0; i < mCharInfoList.size(); i++)
{
const PlatformFont::CharInfo *ci = &mCharInfoList[i];
stream.write(ci->bitmapIndex);
stream.write(ci->xOffset);
stream.write(ci->yOffset);
stream.write(ci->width);
stream.write(ci->height);
stream.write(ci->xOrigin);
stream.write(ci->yOrigin);
stream.write(ci->xIncrement);
}
stream.write(mTextureSheets.size());
for(i = 0; i < mTextureSheets.size(); i++) {
mTextureSheets[i].getBitmap()->writePNG(stream);
}
stream.write(mCurX);
stream.write(mCurY);
stream.write(mCurSheet);
stream.write(minGlyph);
stream.write(maxGlyph);
// Skip it if we don't have any glyphs to do...
if(maxGlyph >= minGlyph)
{
// Put everything big endian, to be consistent. Do this inplace.
for(i = minGlyph; i <= maxGlyph; i++)
mRemapTable[i] = convertHostToBEndian(mRemapTable[i]);
{
// Compress.
const U32 buffSize = 128 * 1024;
FrameTemp<S32> outBuff(buffSize);
uLongf destLen = buffSize * sizeof(S32);
compress2((Bytef*)(S32*)outBuff, &destLen, (Bytef*)(S32*)&mRemapTable[minGlyph], (maxGlyph-minGlyph+1)*sizeof(S32), 9);
// Write out.
stream.write((U32)destLen);
stream.write(destLen, outBuff);
}
// Put us back to normal.
for(i = minGlyph; i <= maxGlyph; i++) {
mRemapTable[i] = convertBEndianToHost(mRemapTable[i]);
//if( mRemapTable[i] == -1 ) {
// Con::errorf( "bogus mRemapTable[i] value in %s %i", mFaceName, mSize );
//}
}
}
return (stream.getStatus() == Stream::Ok);
}
void GFont::exportStrip(const char *fileName, U32 padding, U32 kerning)
{
// Figure dimensions of our strip by iterating over all the char infos.
U32 totalHeight = 0;
U32 totalWidth = 0;
S32 heightMin=0, heightMax=0;
for(S32 i=0; i<mCharInfoList.size(); i++)
{
totalWidth += mCharInfoList[i].width + kerning + 2*padding;
heightMin = getMin((S32)heightMin, (S32)getBaseline() - (S32)mCharInfoList[i].yOrigin);
heightMax = getMax((S32)heightMax, (S32)getBaseline() - (S32)mCharInfoList[i].yOrigin + (S32)mCharInfoList[i].height);
}
totalHeight = heightMax - heightMin + 2*padding;
// Make the bitmap.
GBitmap gb(totalWidth, totalHeight, false, mTextureSheets[0].getBitmap()->getFormat());
dMemset(gb.getWritableBits(), 0, sizeof(U8) * totalHeight * totalWidth );
// Ok, copy some rects, taking into account padding, kerning, offset.
U32 curWidth = kerning + padding;
for(S32 i=0; i<mCharInfoList.size(); i++)
{
// Skip invalid stuff.
if(mCharInfoList[i].bitmapIndex == -1 || mCharInfoList[i].height == 0 || mCharInfoList[i].width == 0)
continue;
// Copy the rect.
U32 bitmap = mCharInfoList[i].bitmapIndex;
RectI ri(mCharInfoList[i].xOffset, mCharInfoList[i].yOffset, mCharInfoList[i].width, mCharInfoList[i].height );
Point2I outRi(curWidth, padding + getBaseline() - mCharInfoList[i].yOrigin);
gb.copyRect(mTextureSheets[bitmap].getBitmap(), ri, outRi);
// Advance.
curWidth += mCharInfoList[i].width + kerning + 2*padding;
}
// Write the image!
FileStream fs;
if(!ResourceManager->openFileForWrite(fs, fileName))
{
Con::errorf("GFont::exportStrip - failed to open '%s' for writing.", fileName);
return;
}
// Done!
gb.writePNG(fs, false);
}
/// Used for repacking in GFont::importStrip.
struct GlyphMap
{
U32 charId;
GBitmap *bitmap;
};
static S32 QSORT_CALLBACK GlyphMapCompare(const void *a, const void *b)
{
S32 ha = ((GlyphMap *) a)->bitmap->height;
S32 hb = ((GlyphMap *) b)->bitmap->height;
return hb - ha;
}
void GFont::importStrip(const char *fileName, U32 padding, U32 kerning)
{
// Wipe our texture sheets, and reload bitmap data from the specified file.
// Also deal with kerning.
// Also, we may have to load RGBA instead of RGB.
// Wipe our texture sheets.
mCurSheet = mCurX = mCurY = 0;
mTextureSheets.clear();
// Now, load the font strip.
GBitmap *strip = GBitmap::load(fileName);
if(!strip)
{
Con::errorf("GFont::importStrip - could not load file '%s'!", fileName);
return;
}
// And get parsing and copying - load up all the characters as separate
// GBitmaps, sort, then pack. Not terribly efficient but this is basically
// on offline task anyway.
// Ok, snag some glyphs.
Vector<GlyphMap> glyphList;
glyphList.reserve(mCharInfoList.size());
U32 curWidth = 0;
for(S32 i=0; i<mCharInfoList.size(); i++)
{
// Skip invalid stuff.
if(mCharInfoList[i].bitmapIndex == -1 || mCharInfoList[i].height == 0 || mCharInfoList[i].width == 0)
continue;
// Allocate a new bitmap for this glyph, taking into account kerning and padding.
glyphList.increment();
glyphList.last().bitmap = new GBitmap(mCharInfoList[i].width + kerning + 2*padding, mCharInfoList[i].height + 2*padding, false, strip->getFormat());
glyphList.last().charId = i;
// Copy the rect.
RectI ri(curWidth, getBaseline() - mCharInfoList[i].yOrigin, glyphList.last().bitmap->width, glyphList.last().bitmap->height);
Point2I outRi(0,0);
glyphList.last().bitmap->copyRect(strip, ri, outRi);
// Update glyph attributes.
mCharInfoList[i].width = glyphList.last().bitmap->width;
mCharInfoList[i].height = glyphList.last().bitmap->height;
mCharInfoList[i].xOffset -= kerning + padding;
mCharInfoList[i].xIncrement += kerning;
mCharInfoList[i].yOffset -= padding;
// Advance.
curWidth += ri.extent.x;
}
// Ok, we have a big list of glyphmaps now. So let's sort them, then pack them.
dQsort(glyphList.address(), glyphList.size(), sizeof(GlyphMap), GlyphMapCompare);
// They're sorted by height, so now we can do some sort of awesome packing.
Point2I curSheetSize(256, 256);
Vector<U32> sheetSizes;
S32 curY = 0;
S32 curX = 0;
S32 curLnHeight = 0;
S32 maxHeight = 0;
for(U32 i = 0; i < (U32)glyphList.size(); i++)
{
PlatformFont::CharInfo *ci = &mCharInfoList[glyphList[i].charId];
if(ci->height > (U32)maxHeight)
maxHeight = ci->height;
if(curX + ci->width > (U32)curSheetSize.x)
{
curY += curLnHeight;
curX = 0;
curLnHeight = 0;
}
if(curY + ci->height > (U32)curSheetSize.y)
{
sheetSizes.push_back(curSheetSize.y);
curX = 0;
curY = 0;
curLnHeight = 0;
}
if(ci->height > (U32)curLnHeight)
curLnHeight = ci->height;
ci->bitmapIndex = sheetSizes.size();
ci->xOffset = curX;
ci->yOffset = curY;
curX += ci->width;
}
// Terminate the packing loop calculations.
curY += curLnHeight;
if(curY < 64)
curSheetSize.y = 64;
else if(curY < 128)
curSheetSize.y = 128;
sheetSizes.push_back(curSheetSize.y);
if(getHeight() + padding * 2 > (U32)maxHeight)
maxHeight = getHeight() + padding * 2;
// Allocate texture pages.
for(S32 i=0; i<sheetSizes.size(); i++)
{
char buf[30];
dSprintf(buf, sizeof(buf), "newfont_%d", smSheetIdCount++);
GBitmap *bitmap = new GBitmap(BaseTextureSheetSize, BaseTextureSheetSize, false, strip->getFormat());
// Set everything to transparent.
U8 *bits = bitmap->getWritableBits();
dMemset(bits, 0, sizeof(U8) *BaseTextureSheetSize*BaseTextureSheetSize * strip->bytesPerPixel);
TextureHandle handle = TextureHandle( buf, bitmap, BitmapKeepTexture );
mTextureSheets.increment();
constructInPlace(&mTextureSheets.last());
mTextureSheets.last() = handle;
}
// Alright, we're ready to copy bits!
for(S32 i=0; i<glyphList.size(); i++)
{
// Copy each glyph into the appropriate place.
PlatformFont::CharInfo *ci = &mCharInfoList[glyphList[i].charId];
U32 bi = ci->bitmapIndex;
mTextureSheets[bi].getBitmap()->copyRect(glyphList[i].bitmap, RectI(0,0, glyphList[i].bitmap->width,glyphList[i].bitmap->height), Point2I(ci->xOffset, ci->yOffset));
}
// Ok, all done! Just refresh some textures and we're set.
for(S32 i=0; i<sheetSizes.size(); i++)
mTextureSheets[i].refresh();
}
ConsoleFunction(fontAtlasTest, void, 1, 1, "")
{
Resource<GFont> f = GFont::create("Arial", 12, Con::getVariable("$GUI::fontCacheDirectory"));
f->getStrWidthPrecise("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 !");
for(int i = 0; i < f->mTextureSheets.size(); i++) {
FileStream fs;
char buf[1024];
dSprintf(buf, 1024, "%s-%u.png", "Arial", i);
fs.open(buf, FileStream::Write);
f->mTextureSheets[i].getBitmap()->writePNG(fs);
}
}
//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
//
// 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.
//-----------------------------------------------------------------------------
#ifndef _GFONT_H_
#define _GFONT_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _GBITMAP_H_
#include "dgl/gBitmap.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _MRECT_H_
#include "math/mRect.h"
#endif
#ifndef _RESMANAGER_H_
#include "core/resManager.h"
#endif
#ifndef _GTEXMANAGER_H_
#include "dgl/gTexManager.h"
#endif
//-Mat use this to make space characters default to a certain x increment
#define PUAP_SPACE_CHAR_X_INCREMENT 5
extern ResourceInstance* constructNewFont(Stream& stream);
class TextureHandle;
class GFont : public ResourceInstance
{
friend ResourceInstance* constructNewFont(Stream& stream);
static const U32 csm_fileVersion;
static S32 smSheetIdCount;
// Enumerations and structs available to everyone...
public:
enum Constants
{
TabWidthInSpaces = 3,
SDFFlag = (1<<31),
VariantHeightFlag = (1<<30),
CustomScaleFlag = (1<<29)
};
// Enumerations and structures available to derived classes
private:
PlatformFont *mPlatformFont;
public:
Vector<TextureHandle> mTextureSheets;
S32 mCurX;
S32 mCurY;
S32 mCurSheet;
bool mNeedSave;
bool mSDF; // Uses a signed distance field
StringTableEntry mGFTFile;
StringTableEntry mFaceName;
U32 mSize;
U32 mCharSet;
U32 mHeight;
U32 mBaseline;
U32 mAscent;
U32 mDescent;
U32 mTexLineHeight; ///< Texture padding may differ, thus this
F32 mTextureScale; ///< Additional scale for font texture (e.g. when doing retina)
F32 mMetricScale; ///< Scale for CharInfo metric values (since they are stored as ints)
Vector<PlatformFont::CharInfo> mCharInfoList; // - List of character info structures, must
// be accessed through the getCharInfo(U32)
// function to account for remapping...
S32 mRemapTable[65536]; // - Index remapping
public:
GFont();
virtual ~GFont();
protected:
bool loadCharInfo(const UTF16 ch);
void addBitmap(PlatformFont::CharInfo &charInfo);
void addSheet(void);
void assignSheet(S32 sheetNum, GBitmap *bmp);
void *mMutex;
public:
static Resource<GFont> create(const char *faceName, U32 size, const char *cacheDirectory, U32 charset = TGE_ANSI_CHARSET);
TextureHandle getTextureHandle(S32 index)
{
return mTextureSheets[index];
}
const PlatformFont::CharInfo& getCharInfo(const UTF16 in_charIndex);
static const PlatformFont::CharInfo& getDefaultCharInfo();
U32 getCharHeight(const UTF16 in_charIndex);
U32 getCharWidth(const UTF16 in_charIndex);
U32 getCharXIncrement(const UTF16 in_charIndex);
bool isValidChar(const UTF16 in_charIndex) const;
const U32 getHeight() const { return mHeight; }
const U32 getBaseline() const { return mBaseline; }
const U32 getAscent() const { return mAscent; }
const U32 getDescent() const { return mDescent; }
const F32 getTextureScale() const { return mTextureScale; }
const F32 getMetricScale() const { return mMetricScale; }
const F32 getInvMetricScale() const { return 1.0f / mMetricScale; }
U32 getBreakPos(const UTF16 *string, U32 strlen, U32 width, bool breakOnWhitespace);
/// These are the preferred width functions.
U32 getStrNWidth(const UTF16*, U32 n);
U32 getStrNWidthPrecise(const UTF16*, U32 n);
/// These UTF8 versions of the width functions will be deprecated, please avoid them.
U32 getStrWidth(const UTF8*); // Note: ignores c/r
U32 getStrNWidth(const UTF8*, U32 n);
U32 getStrWidthPrecise(const UTF8*); // Note: ignores c/r
U32 getStrNWidthPrecise(const UTF8*, U32 n);
void wrapString(const UTF8 *string, U32 width, Vector<U32> &startLineOffset, Vector<U32> &lineLen);
/// Dump information about this font to the console.
void dumpInfo();
/// Export to an image strip for image processing.
void exportStrip(const char *fileName, U32 padding, U32 kerning);
/// Import an image strip generated with exportStrip, make sure parameters match!
void importStrip(const char *fileName, U32 padding, U32 kerning);
/// Query as to presence of platform font. If absent, we cannot generate more
/// chars!
const bool hasPlatformFont() const
{
return mPlatformFont != NULL;
}
/// Query to determine if we should use add or modulate (as A8 textures
/// are treated as having 0 for RGB).
bool isAlphaOnly()
{
return mTextureSheets[0].getBitmap()->getFormat() == GBitmap::Alpha;
}
/// Get the filename for a cached font.
static void getFontCacheFilename(const char *faceName, U32 faceSize, U32 buffLen, char *outBuff);
/// Get the face name of the font.
StringTableEntry getFontFaceName() const { return mFaceName; };
bool read(Stream& io_rStream);
bool write(Stream& io_rStream);
/// Override existing platform font if any with a new one from an external
/// source. This is primarily used in font processing tools to enable
/// trickery (ie, putting characters from multiple fonts in a single
/// GFT) and should be used with caution!
void forcePlatformFont(PlatformFont *pf)
{
mPlatformFont = pf;
}
static U32 BaseTextureSheetSize;
};
inline U32 GFont::getCharXIncrement(const UTF16 in_charIndex)
{
const PlatformFont::CharInfo& rChar = getCharInfo(in_charIndex);
return rChar.xIncrement;
}
inline U32 GFont::getCharWidth(const UTF16 in_charIndex)
{
const PlatformFont::CharInfo& rChar = getCharInfo(in_charIndex);
return rChar.width;
}
inline U32 GFont::getCharHeight(const UTF16 in_charIndex)
{
const PlatformFont::CharInfo& rChar = getCharInfo(in_charIndex);
return rChar.height;
}
inline bool GFont::isValidChar(const UTF16 in_charIndex) const
{
if(mRemapTable[in_charIndex] != -1)
return true;
if(mPlatformFont)
return mPlatformFont->isValidChar(in_charIndex);
return false;
}
#endif //_GFONT_H_
float4 main( float4 color_in : COLOR0,
float2 texCoord_in : TEXCOORD0,
uniform sampler2D diffuseMap : register(S0) ) : COLOR0
{
float distance = tex2D(diffuseMap, texCoord_in).a;
const float w = 0.5/3;
const float threshold = 0.5f;
float alpha = smoothstep( threshold-w, threshold+w, distance );
return float4(color_in.rgb,alpha * color_in.a);
}
AUTHORS
James S Urquhart (jamesu@gmail.com)
//-----------------------------------------------------------------------------
// Copyright (c) 2021 tgemit contributors.
// See AUTHORS file and git repository for contributor information.
//
// SPDX-License-Identifier: MIT
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/unicode.h"
#include "console/console.h"
#include "platformGeneric/stbFont.h"
#include "core/tVector.h"
#include "core/stringTable.h"
#include "core/fileStream.h"
#include <unordered_map>
#include <algorithm>
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb/stb_truetype.h"
Vector<STBFont::SizeCorrection> STBFont::smCorrectedSizes;
Vector<STBFont::FontSubstitution> STBFont::smSubstitutions;
bool gSTBInit;
//
class STBFontCollection
{
public:
struct LoadedInfo
{
U8* data;
StringTableEntry filename;
};
Vector<LoadedInfo> loadedFontData;
std::unordered_map<StringTableEntry, stbtt_fontinfo*> loadedFonts;
static STBFontCollection smInstance;
~STBFontCollection()
{
for (LoadedInfo info : loadedFontData)
{
delete info.data;
}
for (auto &kv : loadedFonts)
{
delete kv.second;
}
loadedFontData.clear();
loadedFonts.clear();
}
bool loadData(const char* path)
{
FileStream fs;
if (fs.open(path, FileStream::Read))
{
U32 len = fs.getStreamSize();
U8* data = new U8[len];
fs.read(len, data);
fs.close();
LoadedInfo info = {data, StringTable->insert(path)};
loadedFontData.push_back(info);
//printf("FONT FILE LOADED %s\n", path);
}
}
void loadDataFolder(const char* path)
{
Vector<Platform::FileInfo> files;
Platform::dumpPath(path, files, 2);
for (Platform::FileInfo &file : files)
{
const char *ext = dStrrchr(file.pFileName, '.');
if (ext && (dStricmp(ext, ".ttf") == 0 || dStricmp(ext, ".otf") == 0))
{
char buffer[4096];
dSprintf(buffer, sizeof(buffer), "%s/%s", file.pFullPath, file.pFileName);
loadData(buffer);
}
}
}
stbtt_fontinfo* findFont(const char* name)
{
char buffer[256];
const char* namePtr = name;
bool valid = false;
stbtt_fontinfo* info = NULL;
StringTableEntry realName = STBFont::getCorrectName(StringTable->insert(name));
auto itr = loadedFonts.find(realName);
if (itr != loadedFonts.end())
{
return info;
}
auto tryLoadFont = [this, &realName, &info](const LoadedInfo dataBlock) {
int fontOffset = stbtt_FindMatchingFont(dataBlock.data, realName, STBTT_MACSTYLE_DONTCARE);
if (fontOffset != -1)
{
info = new stbtt_fontinfo;
if (stbtt_InitFont(info, dataBlock.data, fontOffset) == 0)
{
Con::errorf("STBFontCollection couldn't init font %s!", realName);
delete info;
info = NULL;
}
else
{
loadedFonts[realName] = info;
return true;
}
}
return false;
};
std::find_if(loadedFontData.begin(), loadedFontData.end(), tryLoadFont);
// Try x Regular
if (info == NULL)
{
dSprintf(buffer, 256, "%s Regular", realName);
realName = STBFont::getCorrectName(StringTable->insert(&buffer[0]));
std::find_if(loadedFontData.begin(), loadedFontData.end(), tryLoadFont);
}
return info;
}
};
STBFontCollection STBFontCollection::smInstance;
STBFont::STBFont() :
mBaseline(0),
mHeight(0),
mTexLineHeight(0),
mSize(0),
mScaleMapping(0.0f),
mTextureScale(1.0f),
mMetricScale(10.0f),
mFont(NULL),
mSDF(false)
{
}
STBFont::~STBFont()
{
}
bool STBFont::isValidChar(const UTF16 ch) const
{
return (ch < 0x20) ? false : true;
}
bool STBFont::isValidChar(const UTF8 *str) const
{
UTF32 theChar = oneUTF8toUTF32(str);
return isValidChar(theChar);
}
PlatformFont::CharInfo &STBFont::getCharInfo(const UTF16 ch) const
{
static PlatformFont::CharInfo cinfo;
memset(&cinfo, 0, sizeof(cinfo));
// prep values for GFont::addBitmap()
cinfo.bitmapIndex = 0;
cinfo.xOffset = 0;
cinfo.yOffset = 0;
int leftSideBearing = 0;
int width = 0;
int height = 0;
int __ix0,__iy0,__ix1,__iy1;
int box_w,box_h;
int glyph = stbtt_FindGlyphIndex(mFont, ch);
// Basic metrics
stbtt_GetGlyphHMetrics(mFont, glyph, &cinfo.xIncrement, &leftSideBearing); // leftSideBearing?
stbtt_GetGlyphBitmapBoxSubpixel(mFont, glyph, mScaleMapping, mScaleMapping, 0.0f,0.0f, &__ix0,&__iy0,&__ix1,&__iy1);
box_w = (__ix1 - __ix0);
box_h = (__iy1 - __iy0);
U8* field = NULL;
if (mSDF)
{
// SDF
field = stbtt_GetGlyphSDF(mFont, mScaleMapping * mTextureScale, glyph, 2, 128, 128.0/3.0, &width, &height, &cinfo.xOrigin, &cinfo.yOrigin);
}
else
{
F32 theScale = mScaleMapping * mTextureScale;
field = stbtt_GetGlyphBitmap(mFont, theScale, theScale, glyph, &width, &height, &cinfo.xOrigin, &cinfo.yOrigin);
}
cinfo.width = width;
cinfo.height = height;
// ascent = baseline - yOrigin
cinfo.yOrigin = ((float)-cinfo.yOrigin) * mMetricScale;
cinfo.xOrigin = ((float)cinfo.xOrigin) * mMetricScale;
cinfo.xIncrement *= mScaleMapping * mMetricScale;
if (cinfo.height > getTexLineHeight())
{
Con::warnf("Warning: Not enough texure height for glyph %u, atlas height will be extended.", (U32)ch);
}
// Finish if character is undrawable.
if ( (cinfo.width == 0 && cinfo.height == 0) || field == NULL )
{
if (field && mSDF)
stbtt_FreeSDF(field, NULL);
else if (field && !mSDF)
stbtt_FreeBitmap(field, NULL);
return cinfo;
}
// Allocate a bitmap surface.
const U32 bitmapSize = cinfo.width * cinfo.height;
if (bitmapSize > 0)
{
cinfo.bitmapData = new U8[cinfo.width * cinfo.height];
memcpy(cinfo.bitmapData, field, cinfo.width * cinfo.height);
}
if (field)
{
if (mSDF)
stbtt_FreeSDF(field, NULL);
else
stbtt_FreeBitmap(field, NULL);
}
#if 0
Con::printf("Char %u Width:%f, Height:%f, OriginX:%f, OriginY:%f",
character,
cinfo.width,
cinfo.height,
cinfo.xOrigin,
cinfo.yOrigin );
#endif
// Return character information.
return cinfo;
}
PlatformFont::CharInfo &STBFont::getCharInfo(const UTF8 *str) const
{
return getCharInfo(oneUTF32toUTF16(oneUTF8toUTF32(str, NULL)));
}
bool STBFont::create(const char *name, U32 size, U32 charset)
{
stbtt_fontinfo* info = STBFontCollection::smInstance.findFont(name);
if (info == NULL)
{
if (STBFontCollection::smInstance.loadedFontData.size() != 0)
Con::errorf( "Could not generate a font reference to font name '%s' of size '%d'", name, size );
return false;
}
// See if we have a correction value
StringTableEntry steName = StringTable->insert(name);
for (STBFont::SizeCorrection &correction : smCorrectedSizes)
{
if (correction.name == steName && size == correction.inSize)
{
size = correction.outSize;
break;
}
}
mFont = info;
mSize = size;
mScaleMapping = stbtt_ScaleForMappingEmToPixels(mFont, mSize);
mTextureScale = mSDF ? 2.0 : 1.0f;
mMetricScale = 10.0f;
// Fetch font metrics.
int ascent=0;
int descent=0;
stbtt_GetFontVMetrics(mFont, &ascent, &descent, 0);
stbtt_GetFontVMetricsOS2(mFont, &ascent, &descent, 0);
AssertFatal(descent <= 0, "unexpected value");
int act_height = (ascent + (-descent));
F32 basicDesignScale = (mSize) / (act_height);
// Set metrics
mBaseline = (U32)roundf((F32)ascent * mScaleMapping);
ascent = STBTT_iceil(ascent * mScaleMapping);
descent = STBTT_ifloor(descent * mScaleMapping);
mTexLineHeight = ((ceilf((ascent + (-descent)))) * mTextureScale) + 2 + 2;
mHeight = ceilf((ascent + (-descent)));
return true;
}
StringTableEntry STBFont::getCorrectName(StringTableEntry name)
{
auto itr = std::find_if(smSubstitutions.begin(), smSubstitutions.end(), [name](auto &val){
return name == val.inName;
});
return (itr == smSubstitutions.end() ? name : itr->outName);
}
static void initSTBDefaults()
{
#if defined(TORQUE_OS_WIN32)
STBFontCollection::smInstance.loadDataFolder("C:\\Windows\\Fonts");
#elif defined(TORQUE_OS_LINUX)
STBFontCollection::smInstance.loadDataFolder("/usr/share/fonts/truetype");
STBFontCollection::smInstance.loadDataFolder("/usr/share/fonts/opentype");
#elif defined(TORQUE_OS_OSX)
STBFontCollection::smInstance.loadDataFolder("/System/Library/Fonts");
#endif
gSTBInit = true;
}
void PlatformFont::enumeratePlatformFonts( Vector<StringTableEntry>& fonts )
{
if (!gSTBInit)
{
initSTBDefaults();
}
}
ConsoleFunction(addPlatformFontDirectory, void, 2, 2, "path")
{
gSTBInit = true;
STBFontCollection::smInstance.loadDataFolder(argv[1]);
}
ConsoleFunction(adjustPlatformFontSize, void, 4, 4, "name, old size, new size")
{
STBFont::SizeCorrection size;
size.name = StringTable->insert(argv[1]);
size.inSize = dAtof(argv[2]);
size.outSize = dAtof(argv[3]);
STBFont::smCorrectedSizes.push_back(size);
}
ConsoleFunction(adjustPlatformFontName, void, 3, 3, "old name, new name")
{
STBFont::FontSubstitution correction;
correction.inName = StringTable->insert(argv[1]);
correction.outName = StringTable->insert(argv[2]);
STBFont::smSubstitutions.push_back(correction);
}
PlatformFont* createPlatformFont( const char* name, U32 size, U32 charset )
{
if (!gSTBInit)
{
initSTBDefaults();
}
PlatformFont* pFont = new STBFont();
if ( pFont->create(name, size, charset) )
return pFont;
delete pFont;
return NULL;
}
//-----------------------------------------------------------------------------
// Copyright (c) 2021 tgemit contributors.
// See AUTHORS file and git repository for contributor information.
//
// SPDX-License-Identifier: MIT
//-----------------------------------------------------------------------------
#ifndef _STB_FONT_H_
#define _STB_FONT_H_
#include "platform/platform.h"
#include "core/unicode.h"
#include "stb/stb_truetype.h"
#include <unordered_map>
struct stbtt_fontinfo;
class STBFont : public PlatformFont
{
public:
struct FontSubstitution
{
StringTableEntry inName;
StringTableEntry outName;
};
struct SizeCorrection
{
StringTableEntry name;
U32 inSize;
U32 outSize;
};
U32 mBaseline; // i.e. ascent
U32 mHeight; // distance between lines
U32 mTexLineHeight; // distance + padding between lines in texture
F32 mSize; ///< Point size of font
F32 mScaleMapping; ///< Font scale needed to match mSize (internal)
F32 mTextureScale; ///< Scale of font texture
F32 mMetricScale; ///< Scale of font metrics
bool mSDF;
stbtt_fontinfo* mFont;
STBFont();
virtual ~STBFont();
virtual bool isValidChar(const UTF16 ch) const;
virtual bool isValidChar(const UTF8 *str) const;
virtual bool isSDF() const { return mSDF; }
virtual U32 getFontHeight() const { return mHeight; }
virtual U32 getFontBaseLine() const { return mBaseline; }
virtual U32 getTexLineHeight() const { return mTexLineHeight; }
virtual F32 getTexScale() const { return mTextureScale; }
virtual F32 getMetricScale() const { return mMetricScale; }
virtual PlatformFont::CharInfo &getCharInfo(const UTF16 ch) const;
virtual PlatformFont::CharInfo &getCharInfo(const UTF8 *str) const;
virtual bool create(const char *name, U32 size, U32 charset = TGE_ANSI_CHARSET);
static StringTableEntry getCorrectName(StringTableEntry name);
static Vector<SizeCorrection> smCorrectedSizes;
static Vector<FontSubstitution> smSubstitutions;
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment