//----------------------------------------------------------------------------- // 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 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 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; iisValidChar(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 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 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 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 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; iisValidChar(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 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 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 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::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 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(imapEnd) 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(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 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 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 &startLineOffset, Vector &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 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) { // 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 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; igetFormat()); 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; iopenFileForWrite(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 glyphList; glyphList.reserve(mCharInfoList.size()); U32 curWidth = 0; for(S32 i=0; igetFormat()); 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 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; igetFormat()); // 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; ibitmapIndex; 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