1219 lines
36 KiB
C++
Executable File
1219 lines
36 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "platform/platform.h"
|
|
#include "platform/platformFont.h"
|
|
#include "platform/profiler.h"
|
|
#include "platform/platformMutex.h"
|
|
#include "console/console.h"
|
|
#include "core/stream.h"
|
|
#include "dgl/gBitmap.h"
|
|
#include "core/fileStream.h"
|
|
#include "core/findMatch.h"
|
|
#include "dgl/gTexManager.h"
|
|
#include "dgl/gFont.h"
|
|
#include "util/safeDelete.h"
|
|
#include "core/frameAllocator.h"
|
|
#include "core/unicode.h"
|
|
#include "zlib.h"
|
|
#include "ctype.h" // Needed for isupper and tolower
|
|
|
|
S32 GFont::smSheetIdCount = 0;
|
|
const U32 GFont::csm_fileVersion = 3;
|
|
|
|
ConsoleFunction(populateFontCacheString, void, 4, 4, "(faceName, size, string) - "
|
|
"Populate the font cache for the specified font with characters from the specified string.")
|
|
{
|
|
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.")
|
|
{
|
|
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, "() - Return a full description "
|
|
"of all cached fonts, along with info on the codepoints each contains.")
|
|
{
|
|
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 < 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.")
|
|
{
|
|
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 < 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) - "
|
|
"Populate the font cache for all fonts with characters from the specified string.")
|
|
{
|
|
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 < 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.")
|
|
{
|
|
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 < 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.")
|
|
{
|
|
// Read in some params.
|
|
const char *fontName = argv[1];
|
|
S32 fontSize = dAtoi(argv[2]);
|
|
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.")
|
|
{
|
|
// Read in some params.
|
|
const char *fontName = argv[1];
|
|
S32 fontSize = dAtoi(argv[2]);
|
|
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.")
|
|
{
|
|
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.
|
|
char* fallback = "Helvetica";
|
|
|
|
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* constructFont(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, getCharSetName(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, getCharSetName(charset));
|
|
|
|
Resource<GFont> ret = ResourceManager->load(buf);
|
|
if(bool(ret))
|
|
{
|
|
ret->mGFTFile = StringTable->insert(buf);
|
|
return ret;
|
|
}
|
|
|
|
PlatformFont *platFont = createSafePlatformFont(faceName, size, charset);
|
|
|
|
|
|
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();
|
|
|
|
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;
|
|
|
|
mMutex = Mutex::createMutex();
|
|
}
|
|
|
|
GFont::~GFont()
|
|
{
|
|
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);
|
|
}
|
|
|
|
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;
|
|
|
|
mNeedSave = true;
|
|
|
|
Mutex::unlockMutex(mMutex);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void GFont::addBitmap(PlatformFont::CharInfo &charInfo)
|
|
{
|
|
U32 nextCurX = U32(mCurX + charInfo.width ); /*7) & ~0x3;*/
|
|
U32 nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3;
|
|
|
|
// These are here for postmortem debugging.
|
|
bool routeA = false, routeB = false;
|
|
|
|
if(mCurSheet == -1 || nextCurY >= TextureSheetSize)
|
|
{
|
|
routeA = true;
|
|
addSheet();
|
|
|
|
// Recalc our nexts.
|
|
nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3;
|
|
nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3;
|
|
}
|
|
|
|
if( nextCurX >= TextureSheetSize)
|
|
{
|
|
routeB = true;
|
|
mCurX = 0;
|
|
mCurY = nextCurY;
|
|
|
|
// Recalc our nexts.
|
|
nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3;
|
|
nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3;
|
|
}
|
|
|
|
// Check the Y once more - sometimes we advance to a new row and run off
|
|
// the end.
|
|
if(nextCurY >= TextureSheetSize)
|
|
{
|
|
routeA = true;
|
|
addSheet();
|
|
|
|
// Recalc our nexts.
|
|
nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3;
|
|
nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3;
|
|
}
|
|
|
|
charInfo.bitmapIndex = mCurSheet;
|
|
charInfo.xOffset = mCurX;
|
|
charInfo.yOffset = mCurY;
|
|
|
|
mCurX = nextCurX;
|
|
|
|
S32 x, y;
|
|
GBitmap *bmp = mTextureSheets[mCurSheet].getBitmap();
|
|
|
|
AssertFatal(bmp->getFormat() == GBitmap::Alpha, "GFont::addBitmap - cannot added characters to non-greyscale textures!");
|
|
|
|
for(y = 0;y < charInfo.height;y++)
|
|
for(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++);
|
|
|
|
GBitmap *bitmap = new GBitmap(TextureSheetSize, TextureSheetSize, false, GBitmap::Alpha);
|
|
|
|
// Set everything to transparent.
|
|
U8 *bits = bitmap->getWritableBits();
|
|
dMemset(bits, 0, sizeof(U8) *TextureSheetSize*TextureSheetSize);
|
|
|
|
TextureHandle handle = TextureHandle(buf, bitmap);
|
|
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) const
|
|
{
|
|
PROFILE_START(NewFontGetCharInfo);
|
|
|
|
AssertFatal(in_charIndex, "GFont::getCharInfo - can't get info for char 0!");
|
|
|
|
if(mRemapTable[in_charIndex] == -1)
|
|
{
|
|
// getCharInfo() is const to the outside world, so we cast away the const here
|
|
const_cast<GFont *>(this)->loadCharInfo(in_charIndex);
|
|
}
|
|
|
|
AssertFatal(mRemapTable[in_charIndex] != -1, "No remap info for this character");
|
|
|
|
PROFILE_END();
|
|
|
|
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) const
|
|
{
|
|
AssertFatal(in_pString != NULL, "GFont::getStrWidth: String is NULL, height is undefined");
|
|
// If we ain't running debug...
|
|
if (in_pString == NULL || *in_pString == NULL)
|
|
return 0;
|
|
|
|
return getStrNWidth(in_pString, dStrlen(in_pString));
|
|
}
|
|
|
|
U32 GFont::getStrWidthPrecise(const UTF8* in_pString) const
|
|
{
|
|
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) const
|
|
{
|
|
// 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) const
|
|
{
|
|
AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL");
|
|
|
|
if (str == NULL || str[0] == NULL || n == 0)
|
|
return 0;
|
|
|
|
U32 totWidth = 0;
|
|
UTF16 curChar;
|
|
U32 charCount;
|
|
|
|
for(charCount = 0; charCount < n; charCount++)
|
|
{
|
|
curChar = str[charCount];
|
|
if(curChar == NULL)
|
|
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;
|
|
}
|
|
}
|
|
|
|
return(totWidth);
|
|
}
|
|
|
|
U32 GFont::getStrNWidthPrecise(const UTF8 *str, U32 n) const
|
|
{
|
|
FrameTemp<UTF16> str16(n + 1);
|
|
convertUTF8toUTF16(str, str16, n);
|
|
return getStrNWidthPrecise(str16, n);
|
|
}
|
|
|
|
U32 GFont::getStrNWidthPrecise(const UTF16 *str, U32 n) const
|
|
{
|
|
AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL");
|
|
|
|
if (str == NULL || str[0] == NULL || n == 0)
|
|
return(0);
|
|
|
|
U32 totWidth = 0;
|
|
UTF16 curChar;
|
|
U32 charCount = 0;
|
|
|
|
for(charCount = 0; charCount < n; charCount++)
|
|
{
|
|
curChar = str[charCount];
|
|
if(curChar == NULL)
|
|
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;
|
|
}
|
|
}
|
|
|
|
UTF16 endChar = str[getMin(charCount,n-1)];
|
|
|
|
if (isValidChar(endChar))
|
|
{
|
|
const PlatformFont::CharInfo& rChar = getCharInfo(endChar);
|
|
if (rChar.width > rChar.xIncrement)
|
|
totWidth += (rChar.width - rChar.xIncrement);
|
|
}
|
|
|
|
return(totWidth);
|
|
}
|
|
|
|
U32 GFont::getBreakPos(const UTF16 *str16, U32 slen, U32 width, bool breakOnWhitespace) const
|
|
{
|
|
// Some early out cases.
|
|
if(slen==0)
|
|
return 0;
|
|
|
|
U32 ret = 0;
|
|
U32 lastws = 0;
|
|
UTF16 c;
|
|
U32 charCount = 0;
|
|
|
|
for( charCount=0; charCount < slen; charCount++)
|
|
{
|
|
c = str16[charCount];
|
|
if(c == NULL)
|
|
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 > width || rChar.xIncrement > width)
|
|
{
|
|
if(lastws && breakOnWhitespace)
|
|
return lastws;
|
|
return ret;
|
|
}
|
|
width -= 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;
|
|
|
|
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 > lineWidth )
|
|
{
|
|
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 >= startLine; j--)
|
|
{
|
|
if (dIsspace(txt[j]))
|
|
break;
|
|
}
|
|
|
|
if (j < 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 != csm_fileVersion)
|
|
return false;
|
|
|
|
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);
|
|
|
|
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);
|
|
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]);
|
|
}
|
|
|
|
return (io_rStream.getStatus() == Stream::Ok);
|
|
}
|
|
|
|
bool GFont::write(Stream& stream)
|
|
{
|
|
// Handle versioning
|
|
stream.write(csm_fileVersion);
|
|
|
|
// Write font info
|
|
stream.writeString(mFaceName);
|
|
stream.write(mSize);
|
|
stream.write(mCharSet);
|
|
|
|
stream.write(mHeight);
|
|
stream.write(mBaseline);
|
|
stream.write(mAscent);
|
|
stream.write(mDescent);
|
|
|
|
// Write char info list
|
|
stream.write(U32(mCharInfoList.size()));
|
|
U32 i;
|
|
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);
|
|
|
|
// Get the min/max we have values for, and only write that range out.
|
|
S32 minGlyph = S32_MAX, maxGlyph = 0;
|
|
|
|
for(i = 0; i < 65536; i++)
|
|
{
|
|
if(mRemapTable[i] != -1)
|
|
{
|
|
if(i>maxGlyph) maxGlyph = i;
|
|
if(i<minGlyph) minGlyph = i;
|
|
}
|
|
}
|
|
|
|
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]);
|
|
}
|
|
|
|
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 < glyphList.size(); i++)
|
|
{
|
|
PlatformFont::CharInfo *ci = &mCharInfoList[glyphList[i].charId];
|
|
|
|
if(ci->height > maxHeight)
|
|
maxHeight = ci->height;
|
|
|
|
if(curX + ci->width > curSheetSize.x)
|
|
{
|
|
curY += curLnHeight;
|
|
curX = 0;
|
|
curLnHeight = 0;
|
|
}
|
|
|
|
if(curY + ci->height > curSheetSize.y)
|
|
{
|
|
sheetSizes.push_back(curSheetSize.y);
|
|
curX = 0;
|
|
curY = 0;
|
|
curLnHeight = 0;
|
|
}
|
|
|
|
if(ci->height > 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 > 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(TextureSheetSize, TextureSheetSize, false, strip->getFormat());
|
|
|
|
// Set everything to transparent.
|
|
U8 *bits = bitmap->getWritableBits();
|
|
dMemset(bits, 0, sizeof(U8) *TextureSheetSize*TextureSheetSize * strip->bytesPerPixel);
|
|
|
|
TextureHandle handle = TextureHandle( buf, bitmap );
|
|
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();
|
|
}
|