//----------------------------------------------------------------------------- // 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::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 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 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 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 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 &startLineOffset, Vector &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); }