Last active
January 22, 2024 13:42
-
-
Save jamesu/136f47fcf20b0b775b3e6e60da241aba to your computer and use it in GitHub Desktop.
SDF Font Code prototype
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//----------------------------------------------------------------------------- | |
// 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(¤tSheet, 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(); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//----------------------------------------------------------------------------- | |
// 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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//----------------------------------------------------------------------------- | |
// 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_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AUTHORS | |
James S Urquhart ([email protected]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//----------------------------------------------------------------------------- | |
// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//----------------------------------------------------------------------------- | |
// 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