476 lines
12 KiB
C++
Executable File
476 lines
12 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "core/stream.h"
|
|
#include "dgl/gFont.h"
|
|
#include "dgl/gBitmap.h"
|
|
#include "core/fileStream.h"
|
|
#include "dgl/gTexManager.h"
|
|
|
|
S32 GOldFont::smSheetIdCount = 0;
|
|
|
|
ResourceInstance* constructFont(Stream& stream)
|
|
{
|
|
GOldFont *ret = new GOldFont;
|
|
if(!ret->read(stream))
|
|
{
|
|
delete ret;
|
|
return NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
const U32 GOldFont::csm_fileVersion = 1;
|
|
|
|
Resource<GOldFont> GOldFont::create(const char *faceName, U32 size, const char *cacheDirectory, U32 charset /* = 0 */)
|
|
{
|
|
char buf[256];
|
|
dSprintf(buf, sizeof(buf), "%s/%s_%d.gft", cacheDirectory, faceName, size);
|
|
|
|
Resource<GOldFont> ret = ResourceManager->load(buf);
|
|
if(bool(ret))
|
|
return ret;
|
|
|
|
GOldFont *resFont = createFont(faceName, size, charset);
|
|
if (resFont == NULL)
|
|
{
|
|
AssertISV(dStricmp(faceName, "Arial") != 0, "Error, The Arial Font must always be available!");
|
|
|
|
// Need to handle this case better. For now, let's just return a font that we're
|
|
// positive exists, in the correct size...
|
|
return create("Arial", size, cacheDirectory);
|
|
}
|
|
|
|
FileStream stream;
|
|
if(ResourceManager->openFileForWrite(stream, buf))
|
|
{
|
|
resFont->write(stream);
|
|
stream.close();
|
|
}
|
|
ResourceManager->add(buf, resFont, false);
|
|
return ResourceManager->load(buf);
|
|
}
|
|
|
|
GOldFont::GOldFont()
|
|
{
|
|
VECTOR_SET_ASSOCIATION(mCharInfoList);
|
|
|
|
for (U32 i = 0; i < 256; i++)
|
|
mRemapTable[i] = -1;
|
|
|
|
mTextureSheets = NULL;
|
|
}
|
|
|
|
GOldFont::~GOldFont()
|
|
{
|
|
delete [] mTextureSheets;
|
|
mTextureSheets = NULL;
|
|
}
|
|
|
|
void GOldFont::insertBitmap(U16 index, U8 *src, U32 stride, U32 width, U32 height, S32 xOrigin, S32 yOrigin, S32 xIncrement)
|
|
{
|
|
CharInfo c;
|
|
c.bitmapIndex = -1;
|
|
c.xOffset = 0;
|
|
c.yOffset = 0;
|
|
c.width = width;
|
|
c.height = height;
|
|
|
|
c.xOrigin = xOrigin;
|
|
c.yOrigin = yOrigin;
|
|
c.xIncrement = xIncrement;
|
|
|
|
c.bitmapData = new U8[c.width * c.height];
|
|
|
|
for(U32 y = 0; S32(y) < c.height; y++)
|
|
{
|
|
U32 x;
|
|
for(x = 0; x < width; x++)
|
|
c.bitmapData[y * c.width + x] = src[y * stride + x];
|
|
}
|
|
mRemapTable[index] = mCharInfoList.size();
|
|
mCharInfoList.push_back(c);
|
|
}
|
|
|
|
static S32 QSORT_CALLBACK CharInfoCompare(const void *a, const void *b)
|
|
{
|
|
S32 ha = (*((GOldFont::CharInfo **) a))->height;
|
|
S32 hb = (*((GOldFont::CharInfo **) b))->height;
|
|
|
|
return hb - ha;
|
|
}
|
|
|
|
void GOldFont::pack(U32 inFontHeight, U32 inBaseLine)
|
|
{
|
|
mFontHeight = inFontHeight;
|
|
mBaseLine = inBaseLine;
|
|
|
|
// pack all the bitmap data into sheets.
|
|
Vector<CharInfo *> vec;
|
|
|
|
U32 size = mCharInfoList.size();
|
|
U32 i;
|
|
|
|
for(i = 0; i < size; i++)
|
|
{
|
|
CharInfo *ch = &mCharInfoList[i];
|
|
vec.push_back(ch);
|
|
}
|
|
|
|
dQsort(vec.address(), size, sizeof(CharInfo *), CharInfoCompare);
|
|
// sorted by height
|
|
|
|
Vector<Point2I> sheetSizes;
|
|
Point2I curSheetSize(256, 256);
|
|
|
|
S32 curY = 0;
|
|
S32 curX = 0;
|
|
S32 curLnHeight = 0;
|
|
for(i = 0; i < size; i++)
|
|
{
|
|
CharInfo *ci = vec[i];
|
|
|
|
if(curX + ci->width > curSheetSize.x)
|
|
{
|
|
curY += curLnHeight;
|
|
curX = 0;
|
|
curLnHeight = 0;
|
|
}
|
|
if(curY + ci->height > curSheetSize.y)
|
|
{
|
|
sheetSizes.push_back(curSheetSize);
|
|
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;
|
|
}
|
|
curY += curLnHeight;
|
|
|
|
if(curY < 64)
|
|
curSheetSize.y = 64;
|
|
else if(curY < 128)
|
|
curSheetSize.y = 128;
|
|
|
|
sheetSizes.push_back(curSheetSize);
|
|
|
|
Vector<GBitmap *> bitmapArray;
|
|
|
|
mNumSheets = sheetSizes.size();
|
|
mTextureSheets = new TextureHandle[mNumSheets];
|
|
|
|
for(i = 0; i < mNumSheets; i++)
|
|
bitmapArray.push_back(new GBitmap(sheetSizes[i].x, sheetSizes[i].y, false, GBitmap::Alpha));
|
|
|
|
for(i = 0; i < size; i++)
|
|
{
|
|
CharInfo *ci = vec[i];
|
|
GBitmap *bmp = bitmapArray[ci->bitmapIndex];
|
|
S32 x, y;
|
|
for(y = 0; y < ci->height; y++)
|
|
for(x = 0; x < ci->width; x++)
|
|
*bmp->getAddress(x + ci->xOffset, y + ci->yOffset) =
|
|
ci->bitmapData[y * ci->width + x];
|
|
delete[] ci->bitmapData;
|
|
}
|
|
for(i = 0; i < mNumSheets; i++)
|
|
{
|
|
assignSheet(i, bitmapArray[i]);
|
|
mTextureSheets[i].setFilterNearest();
|
|
}
|
|
}
|
|
|
|
TextureHandle GOldFont::getTextureHandle(S32 index)
|
|
{
|
|
return mTextureSheets[index];
|
|
}
|
|
|
|
void GOldFont::assignSheet(S32 sheetNum, GBitmap *bmp)
|
|
{
|
|
char buf[30];
|
|
dSprintf(buf, sizeof(buf), "font_%d", smSheetIdCount++);
|
|
mTextureSheets[sheetNum] = TextureHandle(buf, bmp);
|
|
}
|
|
|
|
U32 GOldFont::getStrWidth(const char* in_pString) const
|
|
{
|
|
AssertFatal(in_pString != NULL, "GOldFont::getStrWidth: String is NULL, height is undefined");
|
|
// If we ain't running debug...
|
|
if (in_pString == NULL)
|
|
return 0;
|
|
|
|
return getStrNWidth(in_pString, dStrlen(in_pString));
|
|
}
|
|
|
|
U32 GOldFont::getStrWidthPrecise(const char* in_pString) const
|
|
{
|
|
AssertFatal(in_pString != NULL, "GOldFont::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 GOldFont::getStrNWidth(const char *str, U32 n) const
|
|
{
|
|
AssertFatal(str != NULL, "GOldFont::getStrNWidth: String is NULL");
|
|
|
|
if (str == NULL)
|
|
return(0);
|
|
|
|
U32 totWidth = 0;
|
|
const char *curChar;
|
|
const char *endStr;
|
|
for (curChar = str, endStr = str + n; curChar < endStr; curChar++)
|
|
{
|
|
if(isValidChar(*curChar))
|
|
{
|
|
const CharInfo& rChar = getCharInfo(*curChar);
|
|
totWidth += rChar.xIncrement;
|
|
}
|
|
else if (*curChar == '\t')
|
|
{
|
|
const CharInfo& rChar = getCharInfo(' ');
|
|
totWidth += rChar.xIncrement * TabWidthInSpaces;
|
|
}
|
|
}
|
|
|
|
return(totWidth);
|
|
}
|
|
|
|
U32 GOldFont::getStrNWidthPrecise(const char *str, U32 n) const
|
|
{
|
|
AssertFatal(str != NULL, "GOldFont::getStrNWidth: String is NULL");
|
|
|
|
if (str == NULL)
|
|
return(0);
|
|
|
|
U32 totWidth = 0;
|
|
const char *curChar;
|
|
const char *endStr;
|
|
for (curChar = str, endStr = str + n; curChar < endStr; curChar++)
|
|
{
|
|
if(isValidChar(*curChar))
|
|
{
|
|
const CharInfo& rChar = getCharInfo(*curChar);
|
|
totWidth += rChar.xIncrement;
|
|
}
|
|
else if (*curChar == '\t')
|
|
{
|
|
const CharInfo& rChar = getCharInfo(' ');
|
|
totWidth += rChar.xIncrement * TabWidthInSpaces;
|
|
}
|
|
}
|
|
|
|
if (n != 0)
|
|
{
|
|
// Need to check the last char to see if it has some slop...
|
|
char endChar = str[n-1];
|
|
if (isValidChar(endChar))
|
|
{
|
|
const CharInfo& rChar = getCharInfo(endChar);
|
|
if (rChar.width > rChar.xIncrement)
|
|
totWidth += (rChar.width - rChar.xIncrement);
|
|
}
|
|
}
|
|
|
|
return(totWidth);
|
|
}
|
|
|
|
U32 GOldFont::getBreakPos(const char *string, U32 slen, U32 width, bool breakOnWhitespace)
|
|
{
|
|
U32 ret = 0;
|
|
U32 lastws = 0;
|
|
while(ret < slen)
|
|
{
|
|
char c = string[ret];
|
|
if(c == '\t')
|
|
c = ' ';
|
|
if(!isValidChar(c))
|
|
{
|
|
ret++;
|
|
continue;
|
|
}
|
|
if(c == ' ')
|
|
lastws = ret+1;
|
|
const 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 GOldFont::wrapString(const char *txt, U32 lineWidth, Vector<U32> &startLineOffset, Vector<U32> &lineLen)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
//-------------------------------------- Persist functionality
|
|
//
|
|
static const U32 csm_fileVersion = 1;
|
|
|
|
bool GOldFont::read(Stream& io_rStream)
|
|
{
|
|
// Handle versioning
|
|
U32 version;
|
|
io_rStream.read(&version);
|
|
if(version != csm_fileVersion)
|
|
return false;
|
|
|
|
// Read Font Information
|
|
io_rStream.read(&mFontHeight);
|
|
io_rStream.read(&mBaseLine);
|
|
|
|
U32 size = 0;
|
|
io_rStream.read(&size);
|
|
mCharInfoList.setSize(size);
|
|
U32 i;
|
|
for(i = 0; i < size; i++)
|
|
{
|
|
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);
|
|
}
|
|
io_rStream.read(&mNumSheets);
|
|
|
|
mTextureSheets = new TextureHandle[mNumSheets];
|
|
for(i = 0; i < mNumSheets; i++)
|
|
{
|
|
GBitmap *bmp = new GBitmap;
|
|
if(!bmp->readPNG(io_rStream))
|
|
{
|
|
delete bmp;
|
|
return false;
|
|
}
|
|
assignSheet(i, bmp);
|
|
mTextureSheets[i].setFilterNearest();
|
|
}
|
|
|
|
// Read character remap table
|
|
for(i = 0; i < 256; i++)
|
|
io_rStream.read(&mRemapTable[i]);
|
|
|
|
return (io_rStream.getStatus() == Stream::Ok);
|
|
}
|
|
|
|
bool
|
|
GOldFont::write(Stream& stream) const
|
|
{
|
|
// Handle versioning
|
|
stream.write(csm_fileVersion);
|
|
|
|
// Write Font Information
|
|
stream.write(mFontHeight);
|
|
stream.write(mBaseLine);
|
|
|
|
stream.write(U32(mCharInfoList.size()));
|
|
U32 i;
|
|
for(i = 0; i < mCharInfoList.size(); i++)
|
|
{
|
|
const 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(mNumSheets);
|
|
for(i = 0; i < mNumSheets; i++)
|
|
mTextureSheets[i].getBitmap()->writePNG(stream);
|
|
|
|
for(i = 0; i < 256; i++)
|
|
stream.write(mRemapTable[i]);
|
|
|
|
return (stream.getStatus() == Stream::Ok);
|
|
}
|