gFont.cpp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "gfx/gFont.h"
  24. #include "core/resourceManager.h"
  25. #include "core/stream/fileStream.h"
  26. #include "core/strings/unicode.h"
  27. #include "core/strings/findMatch.h"
  28. #include "core/strings/stringFunctions.h"
  29. #include "core/util/endian.h"
  30. #include "core/util/safeDelete.h"
  31. #include "console/console.h"
  32. #include "console/engineAPI.h"
  33. #include "platform/threads/mutex.h"
  34. #include "zlib/zlib.h"
  35. GFX_ImplementTextureProfile(GFXFontTextureProfile,
  36. GFXTextureProfile::DiffuseMap,
  37. GFXTextureProfile::PreserveSize |
  38. GFXTextureProfile::Static |
  39. GFXTextureProfile::KeepBitmap |
  40. GFXTextureProfile::NoMipmap,
  41. GFXTextureProfile::NONE);
  42. template<> void *Resource<GFont>::create(const Torque::Path &path)
  43. {
  44. #ifdef TORQUE_DEBUG_RES_MANAGER
  45. Con::printf( "Resource<GFont>::create - [%s]", path.getFullPath().c_str() );
  46. #endif
  47. return GFont::load( path );
  48. }
  49. template<> ResourceBase::Signature Resource<GFont>::signature()
  50. {
  51. return MakeFourCC('f','o','n','t');
  52. }
  53. /// Used for repacking in GFont::importStrip.
  54. struct GlyphMap
  55. {
  56. U32 charId;
  57. GBitmap *bitmap;
  58. };
  59. static S32 QSORT_CALLBACK GlyphMapCompare(const void *a, const void *b)
  60. {
  61. S32 ha = ((GlyphMap *) a)->bitmap->getHeight();
  62. S32 hb = ((GlyphMap *) b)->bitmap->getHeight();
  63. return hb - ha;
  64. }
  65. const U32 GFont::csm_fileVersion = 3;
  66. String GFont::getFontCacheFilename(const String &faceName, U32 size)
  67. {
  68. return String::ToString("%s/%s %d (%s).uft",
  69. Con::getVariable("$GUI::fontCacheDirectory"), faceName.c_str(), size, getCharSetName(0));
  70. }
  71. GFont* GFont::load( const Torque::Path& path )
  72. {
  73. FileStream stream;
  74. stream.open( path.getFullPath(), Torque::FS::File::Read );
  75. if ( stream.getStatus() != Stream::Ok )
  76. return NULL;
  77. GFont *ret = new GFont;
  78. ret->mGFTFile = path;
  79. if(!ret->read(stream))
  80. {
  81. Con::errorf( "GFont::load - error reading '%s'", path.getFullPath().c_str() );
  82. SAFE_DELETE(ret);
  83. }
  84. else
  85. {
  86. PlatformFont *platFont = createPlatformFont(ret->getFontFaceName(), ret->getFontSize(), ret->getFontCharSet());
  87. if ( platFont == NULL )
  88. {
  89. Con::errorf( "GFont::load - error creating platform font for '%s'", path.getFullPath().c_str() );
  90. SAFE_DELETE(ret);
  91. }
  92. else
  93. ret->setPlatformFont(platFont);
  94. }
  95. return ret;
  96. }
  97. Resource<GFont> GFont::create(const String &faceName, U32 size, const char *cacheDirectory, U32 charset /* = TGE_ANSI_CHARSET */)
  98. {
  99. if( !cacheDirectory )
  100. cacheDirectory = Con::getVariable( "$GUI::fontCacheDirectory" );
  101. const Torque::Path path( String::ToString("%s/%s %d (%s).uft",
  102. cacheDirectory, faceName.c_str(), size, getCharSetName(charset)) );
  103. Resource<GFont> ret;
  104. // If the file already exists attempt to load it
  105. if (Platform::isFile(path.getFullPath().c_str()))
  106. {
  107. ret = ResourceManager::get().load(path);
  108. if (ret != NULL)
  109. {
  110. ret->mGFTFile = path;
  111. return ret;
  112. }
  113. }
  114. // Otherwise attempt to have the platform generate a new font
  115. PlatformFont *platFont = createPlatformFont(faceName, size, charset);
  116. if (platFont == NULL)
  117. {
  118. String fontName;
  119. #ifdef _XBOX
  120. //AssertFatal( false, "Font creation is not supported on the Xbox platform. Create the font files (*.uft) using the Windows/MacOS build of the project." );
  121. if(!faceName.equal("arial", String::NoCase) || size != 14)
  122. {
  123. return create("Arial", 14, cacheDirectory, charset);
  124. }
  125. return ret;
  126. #endif
  127. // Couldn't load the requested font. This probably will be common
  128. // since many unix boxes don't have arial or lucida console installed.
  129. // Attempt to map the font name into a font we're pretty sure exist
  130. // Lucida Console is a common code & console font on windows, and
  131. // Monaco is the recommended code & console font on mac.
  132. if (faceName.equal("arial", String::NoCase))
  133. fontName = "Helvetica";
  134. else if (faceName.equal("lucida console", String::NoCase))
  135. fontName = "Monaco";
  136. else if (faceName.equal("monaco", String::NoCase))
  137. fontName = "Courier";
  138. else
  139. return ret;
  140. return create(fontName, size, cacheDirectory, charset);
  141. }
  142. // Create the actual GFont and set some initial properties
  143. GFont *font = new GFont;
  144. font->mPlatformFont = platFont;
  145. font->mGFTFile = path;
  146. font->mFaceName = faceName;
  147. font->mSize = size;
  148. font->mCharSet = charset;
  149. font->mHeight = platFont->getFontHeight();
  150. font->mBaseline = platFont->getFontBaseLine();
  151. font->mAscent = platFont->getFontBaseLine();
  152. font->mDescent = platFont->getFontHeight() - platFont->getFontBaseLine();
  153. // Flag it to save when we exit
  154. font->mNeedSave = true;
  155. // Load the newly created font into the ResourceManager
  156. ret.setResource(ResourceManager::get().load(path), font);
  157. return ret;
  158. }
  159. //-------------------------------------------------------------------------
  160. GFont::GFont()
  161. {
  162. VECTOR_SET_ASSOCIATION(mCharInfoList);
  163. VECTOR_SET_ASSOCIATION(mTextureSheets);
  164. for (U32 i = 0; i < (sizeof(mRemapTable) / sizeof(S32)); i++)
  165. mRemapTable[i] = -1;
  166. mCurX = mCurY = mCurSheet = -1;
  167. mPlatformFont = NULL;
  168. mSize = 0;
  169. mCharSet = 0;
  170. mNeedSave = false;
  171. mMutex = Mutex::createMutex();
  172. }
  173. GFont::~GFont()
  174. {
  175. if(mNeedSave)
  176. {
  177. AssertFatal( mGFTFile.getFullPath().isNotEmpty(), "GFont::~GFont - path not set" );
  178. FileStream stream;
  179. stream.open(mGFTFile, Torque::FS::File::Write);
  180. if(stream.getStatus() == Stream::Ok)
  181. write(stream);
  182. stream.close();
  183. }
  184. S32 i;
  185. for(i = 0;i < mCharInfoList.size();i++)
  186. {
  187. SAFE_DELETE_ARRAY(mCharInfoList[i].bitmapData);
  188. }
  189. for(i=0; i<mTextureSheets.size(); i++)
  190. mTextureSheets[i] = NULL;
  191. SAFE_DELETE(mPlatformFont);
  192. Mutex::destroyMutex(mMutex);
  193. }
  194. void GFont::dumpInfo() const
  195. {
  196. // Number and extent of mapped characters?
  197. U32 mapCount = 0, mapBegin=0xFFFF, mapEnd=0;
  198. for(U32 i=0; i<0x10000; i++)
  199. {
  200. if(mRemapTable[i] != -1)
  201. {
  202. mapCount++;
  203. if(i<mapBegin) mapBegin = i;
  204. if(i>mapEnd) mapEnd = i;
  205. }
  206. }
  207. // Let's write out all the info we can on this font.
  208. Con::printf(" '%s' %dpt", mFaceName.c_str(), mSize);
  209. Con::printf(" - %d texture sheets, %d mapped characters.", mTextureSheets.size(), mapCount);
  210. if(mapCount)
  211. Con::printf(" - Codepoints range from 0x%x to 0x%x.", mapBegin, mapEnd);
  212. else
  213. Con::printf(" - No mapped codepoints.");
  214. Con::printf(" - Platform font is %s.", (mPlatformFont ? "present" : "not present") );
  215. }
  216. //-----------------------------------------------------------------------------
  217. bool GFont::loadCharInfo(const UTF16 ch)
  218. {
  219. PROFILE_SCOPE(GFont_loadCharInfo);
  220. if(mRemapTable[ch] != -1)
  221. return true; // Not really an error
  222. if(mPlatformFont && mPlatformFont->isValidChar(ch))
  223. {
  224. Mutex::lockMutex(mMutex); // the CharInfo returned by mPlatformFont is static data, must protect from changes.
  225. PlatformFont::CharInfo &ci = mPlatformFont->getCharInfo(ch);
  226. if(ci.bitmapData)
  227. addBitmap(ci);
  228. mCharInfoList.push_back(ci);
  229. mRemapTable[ch] = mCharInfoList.size() - 1;
  230. mNeedSave = true;
  231. Mutex::unlockMutex(mMutex);
  232. return true;
  233. }
  234. return false;
  235. }
  236. void GFont::addBitmap(PlatformFont::CharInfo &charInfo)
  237. {
  238. U32 nextCurX = U32(mCurX + charInfo.width ); /*7) & ~0x3;*/
  239. U32 nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3;
  240. // These are here for postmortem debugging.
  241. bool routeA = false, routeB = false;
  242. if(mCurSheet == -1 || nextCurY >= TextureSheetSize)
  243. {
  244. routeA = true;
  245. addSheet();
  246. // Recalc our nexts.
  247. nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3;
  248. nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3;
  249. }
  250. if( nextCurX >= TextureSheetSize)
  251. {
  252. routeB = true;
  253. mCurX = 0;
  254. mCurY = nextCurY;
  255. // Recalc our nexts.
  256. nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3;
  257. nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3;
  258. }
  259. // Check the Y once more - sometimes we advance to a new row and run off
  260. // the end.
  261. if(nextCurY >= TextureSheetSize)
  262. {
  263. routeA = true;
  264. addSheet();
  265. // Recalc our nexts.
  266. nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3;
  267. nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3;
  268. }
  269. charInfo.bitmapIndex = mCurSheet;
  270. charInfo.xOffset = mCurX;
  271. charInfo.yOffset = mCurY;
  272. mCurX = nextCurX;
  273. S32 x, y;
  274. GBitmap *bmp = mTextureSheets[mCurSheet].getBitmap();
  275. AssertFatal(bmp->getFormat() == GFXFormatA8, "GFont::addBitmap - cannot added characters to non-greyscale textures!");
  276. for(y = 0;y < charInfo.height;y++)
  277. for(x = 0;x < charInfo.width;x++)
  278. *bmp->getAddress(x + charInfo.xOffset, y + charInfo.yOffset) = charInfo.bitmapData[y * charInfo.width + x];
  279. mTextureSheets[mCurSheet].refresh();
  280. }
  281. void GFont::addSheet()
  282. {
  283. GBitmap *bitmap = new GBitmap(TextureSheetSize, TextureSheetSize, false, GFXFormatA8 );
  284. // Set everything to transparent.
  285. U8 *bits = bitmap->getWritableBits();
  286. dMemset(bits, 0, sizeof(U8) *TextureSheetSize*TextureSheetSize);
  287. GFXTexHandle handle = GFXTexHandle( bitmap, &GFXFontTextureProfile, true, avar("%s() - (line %d)", __FUNCTION__, __LINE__) );
  288. mTextureSheets.increment();
  289. mTextureSheets.last() = handle;
  290. mCurX = 0;
  291. mCurY = 0;
  292. mCurSheet = mTextureSheets.size() - 1;
  293. }
  294. //-----------------------------------------------------------------------------
  295. const PlatformFont::CharInfo &GFont::getCharInfo(const UTF16 in_charIndex)
  296. {
  297. PROFILE_SCOPE(GFont_getCharInfo);
  298. AssertFatal(in_charIndex, "GFont::getCharInfo - can't get info for char 0!");
  299. if(mRemapTable[in_charIndex] == -1)
  300. loadCharInfo(in_charIndex);
  301. AssertFatal(mRemapTable[in_charIndex] != -1, "No remap info for this character");
  302. return mCharInfoList[mRemapTable[in_charIndex]];
  303. }
  304. const PlatformFont::CharInfo &GFont::getDefaultCharInfo()
  305. {
  306. static PlatformFont::CharInfo c;
  307. // c is initialized by the CharInfo default constructor.
  308. return c;
  309. }
  310. //-----------------------------------------------------------------------------
  311. U32 GFont::getStrWidth(const UTF8* in_pString)
  312. {
  313. AssertFatal(in_pString != NULL, "GFont::getStrWidth: String is NULL, height is undefined");
  314. // If we ain't running debug...
  315. if (in_pString == NULL || *in_pString == '\0')
  316. return 0;
  317. return getStrNWidth(in_pString, dStrlen(in_pString));
  318. }
  319. U32 GFont::getStrWidthPrecise(const UTF8* in_pString)
  320. {
  321. AssertFatal(in_pString != NULL, "GFont::getStrWidth: String is NULL, height is undefined");
  322. // If we ain't running debug...
  323. if (in_pString == NULL)
  324. return 0;
  325. return getStrNWidthPrecise(in_pString, dStrlen(in_pString));
  326. }
  327. //-----------------------------------------------------------------------------
  328. U32 GFont::getStrNWidth(const UTF8 *str, U32 n)
  329. {
  330. // UTF8 conversion is expensive. Avoid converting in a tight loop.
  331. FrameTemp<UTF16> str16(n + 1);
  332. convertUTF8toUTF16N(str, str16, n + 1);
  333. return getStrNWidth(str16, dStrlen(str16));
  334. }
  335. U32 GFont::getStrNWidth(const UTF16 *str, U32 n)
  336. {
  337. AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL");
  338. if (str == NULL || str[0] == '\0' || n == 0)
  339. return 0;
  340. U32 totWidth = 0;
  341. UTF16 curChar;
  342. U32 charCount;
  343. for(charCount = 0; charCount < n; charCount++)
  344. {
  345. curChar = str[charCount];
  346. if(curChar == '\0')
  347. break;
  348. if(isValidChar(curChar))
  349. {
  350. const PlatformFont::CharInfo& rChar = getCharInfo(curChar);
  351. totWidth += rChar.xIncrement;
  352. }
  353. else if (curChar == dT('\t'))
  354. {
  355. const PlatformFont::CharInfo& rChar = getCharInfo(dT(' '));
  356. totWidth += rChar.xIncrement * TabWidthInSpaces;
  357. }
  358. }
  359. return(totWidth);
  360. }
  361. U32 GFont::getStrNWidthPrecise(const UTF8 *str, U32 n)
  362. {
  363. FrameTemp<UTF16> str16(n + 1);
  364. convertUTF8toUTF16N(str, str16, n + 1);
  365. return getStrNWidthPrecise(str16, dStrlen(str16));
  366. }
  367. U32 GFont::getStrNWidthPrecise(const UTF16 *str, U32 n)
  368. {
  369. AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL");
  370. if (str == NULL || str[0] == '\0' || n == 0)
  371. return(0);
  372. U32 totWidth = 0;
  373. UTF16 curChar;
  374. U32 charCount = 0;
  375. for(charCount = 0; charCount < n; charCount++)
  376. {
  377. curChar = str[charCount];
  378. if(curChar == '\0')
  379. break;
  380. if(isValidChar(curChar))
  381. {
  382. const PlatformFont::CharInfo& rChar = getCharInfo(curChar);
  383. totWidth += rChar.xIncrement;
  384. }
  385. else if (curChar == dT('\t'))
  386. {
  387. const PlatformFont::CharInfo& rChar = getCharInfo(dT(' '));
  388. totWidth += rChar.xIncrement * TabWidthInSpaces;
  389. }
  390. }
  391. UTF16 endChar = str[getMin(charCount,n-1)];
  392. if (isValidChar(endChar))
  393. {
  394. const PlatformFont::CharInfo& rChar = getCharInfo(endChar);
  395. if (rChar.width != rChar.xIncrement)
  396. totWidth += (rChar.width - rChar.xIncrement);
  397. }
  398. return(totWidth);
  399. }
  400. U32 GFont::getBreakPos(const UTF16 *str16, U32 slen, U32 width, bool breakOnWhitespace)
  401. {
  402. // Some early out cases.
  403. if(slen==0)
  404. return 0;
  405. U32 ret = 0;
  406. U32 lastws = 0;
  407. UTF16 c;
  408. U32 charCount = 0;
  409. for( charCount=0; charCount < slen; charCount++)
  410. {
  411. c = str16[charCount];
  412. if(c == '\0')
  413. break;
  414. if(c == dT('\t'))
  415. c = dT(' ');
  416. if(!isValidChar(c))
  417. {
  418. ret++;
  419. continue;
  420. }
  421. if(c == dT(' '))
  422. lastws = ret+1;
  423. const PlatformFont::CharInfo& rChar = getCharInfo(c);
  424. if(rChar.width > width || rChar.xIncrement > width)
  425. {
  426. if(lastws && breakOnWhitespace)
  427. return lastws;
  428. return ret;
  429. }
  430. width -= rChar.xIncrement;
  431. ret++;
  432. }
  433. return ret;
  434. }
  435. void GFont::wrapString(const UTF8 *txt, U32 lineWidth, Vector<U32> &startLineOffset, Vector<U32> &lineLen)
  436. {
  437. // TODO: Is this error still true?
  438. //Con::errorf("GFont::wrapString(): Not yet converted to be UTF-8 safe");
  439. startLineOffset.clear();
  440. lineLen.clear();
  441. if (!txt || !txt[0] || lineWidth < getCharWidth('W')) //make sure the line width is greater then a single character
  442. return;
  443. U32 len = dStrlen(txt);
  444. U32 startLine;
  445. for (U32 i = 0; i < len;)
  446. {
  447. U32 wide = 0;
  448. startLine = i;
  449. startLineOffset.push_back(startLine);
  450. // loop until the string is too large
  451. bool needsNewLine = false;
  452. U32 lineStrWidth = 0;
  453. for (; i < len; i++)
  454. {
  455. if( txt[ i ] == '\n' )
  456. {
  457. needsNewLine = true;
  458. break;
  459. }
  460. else if(isValidChar(txt[i]))
  461. {
  462. lineStrWidth += getCharInfo(txt[i]).xIncrement;
  463. if(txt[i] < 0) // symbols which code > 127
  464. {
  465. wide++; i++;
  466. }
  467. if( lineStrWidth > lineWidth )
  468. {
  469. needsNewLine = true;
  470. break;
  471. }
  472. }
  473. }
  474. if (!needsNewLine)
  475. {
  476. // we are done!
  477. lineLen.push_back(i - startLine - wide);
  478. return;
  479. }
  480. S32 j;
  481. // Did we hit a hardwrap (newline character) in the string.
  482. bool hardwrap = ( txt[i] == '\n' );
  483. if ( hardwrap )
  484. {
  485. j = i;
  486. }
  487. // determine where to put the newline
  488. // we need to backtrack until we find a space character
  489. // we don't do this for hardwrap(s)
  490. else
  491. {
  492. for (j = i - 1; j >= startLine; j--)
  493. {
  494. if ( dIsspace(txt[j]) || txt[i] == '\n' )
  495. break;
  496. }
  497. if (j < startLine)
  498. {
  499. // the line consists of a single word!
  500. // So, just break up the word
  501. j = i - 1;
  502. }
  503. }
  504. lineLen.push_back(j - startLine - wide);
  505. i = j;
  506. // Now we need to increment through any space characters at the
  507. // beginning of the next line.
  508. // We don't skip spaces after a hardwrap because they were obviously intended.
  509. for (i++; i < len; i++)
  510. {
  511. if ( txt[i] == '\n' )
  512. continue;
  513. if ( dIsspace( txt[i] ) && !hardwrap )
  514. continue;
  515. break;
  516. }
  517. }
  518. }
  519. //-----------------------------------------------------------------------------
  520. bool GFont::read(Stream& io_rStream)
  521. {
  522. // Handle versioning
  523. U32 version;
  524. io_rStream.read(&version);
  525. if(version != csm_fileVersion)
  526. return false;
  527. char buf[256];
  528. io_rStream.readString(buf);
  529. mFaceName = buf;
  530. io_rStream.read(&mSize);
  531. io_rStream.read(&mCharSet);
  532. io_rStream.read(&mHeight);
  533. io_rStream.read(&mBaseline);
  534. io_rStream.read(&mAscent);
  535. io_rStream.read(&mDescent);
  536. U32 size = 0;
  537. io_rStream.read(&size);
  538. mCharInfoList.setSize(size);
  539. U32 i;
  540. for(i = 0; i < size; i++)
  541. {
  542. PlatformFont::CharInfo *ci = &mCharInfoList[i];
  543. io_rStream.read(&ci->bitmapIndex);
  544. io_rStream.read(&ci->xOffset);
  545. io_rStream.read(&ci->yOffset);
  546. io_rStream.read(&ci->width);
  547. io_rStream.read(&ci->height);
  548. io_rStream.read(&ci->xOrigin);
  549. io_rStream.read(&ci->yOrigin);
  550. io_rStream.read(&ci->xIncrement);
  551. ci->bitmapData = NULL;
  552. }
  553. U32 numSheets = 0;
  554. io_rStream.read(&numSheets);
  555. for(i = 0; i < numSheets; i++)
  556. {
  557. GBitmap *bmp = new GBitmap;
  558. if(!bmp->readBitmap("png", io_rStream))
  559. {
  560. delete bmp;
  561. return false;
  562. }
  563. GFXTexHandle handle = GFXTexHandle(bmp, &GFXFontTextureProfile, true, avar("%s() - Read Font Sheet for %s %d (line %d)", __FUNCTION__, mFaceName.c_str(), mSize, __LINE__));
  564. //handle.setFilterNearest();
  565. mTextureSheets.push_back(handle);
  566. }
  567. // Read last position info
  568. io_rStream.read(&mCurX);
  569. io_rStream.read(&mCurY);
  570. io_rStream.read(&mCurSheet);
  571. // Read the remap table.
  572. U32 minGlyph, maxGlyph;
  573. io_rStream.read(&minGlyph);
  574. io_rStream.read(&maxGlyph);
  575. if(maxGlyph >= minGlyph)
  576. {
  577. // Length of buffer..
  578. U32 buffLen;
  579. io_rStream.read(&buffLen);
  580. // Read the buffer.
  581. FrameTemp<S32> inBuff(buffLen);
  582. io_rStream.read(buffLen, inBuff);
  583. // Decompress.
  584. uLongf destLen = (maxGlyph-minGlyph+1)*sizeof(S32);
  585. uncompress((Bytef*)&mRemapTable[minGlyph], &destLen, (Bytef*)(S32*)inBuff, buffLen);
  586. AssertISV(destLen == (maxGlyph-minGlyph+1)*sizeof(S32), "GFont::read - invalid remap table data!");
  587. // Make sure we've got the right endianness.
  588. for(i = minGlyph; i <= maxGlyph; i++)
  589. mRemapTable[i] = convertBEndianToHost(mRemapTable[i]);
  590. }
  591. return (io_rStream.getStatus() == Stream::Ok);
  592. }
  593. bool GFont::write(Stream& stream)
  594. {
  595. // Handle versioning
  596. stream.write(csm_fileVersion);
  597. // Write font info
  598. stream.write(mFaceName);
  599. stream.write(mSize);
  600. stream.write(mCharSet);
  601. stream.write(mHeight);
  602. stream.write(mBaseline);
  603. stream.write(mAscent);
  604. stream.write(mDescent);
  605. // Write char info list
  606. stream.write(U32(mCharInfoList.size()));
  607. U32 i;
  608. for(i = 0; i < mCharInfoList.size(); i++)
  609. {
  610. const PlatformFont::CharInfo *ci = &mCharInfoList[i];
  611. stream.write(ci->bitmapIndex);
  612. stream.write(ci->xOffset);
  613. stream.write(ci->yOffset);
  614. stream.write(ci->width);
  615. stream.write(ci->height);
  616. stream.write(ci->xOrigin);
  617. stream.write(ci->yOrigin);
  618. stream.write(ci->xIncrement);
  619. }
  620. stream.write(mTextureSheets.size());
  621. for(i = 0; i < mTextureSheets.size(); i++)
  622. mTextureSheets[i].getBitmap()->writeBitmap("png", stream);
  623. stream.write(mCurX);
  624. stream.write(mCurY);
  625. stream.write(mCurSheet);
  626. // Get the min/max we have values for, and only write that range out.
  627. S32 minGlyph = S32_MAX, maxGlyph = 0;
  628. for(i = 0; i < 65536; i++)
  629. {
  630. if(mRemapTable[i] != -1)
  631. {
  632. if(i>maxGlyph) maxGlyph = i;
  633. if(i<minGlyph) minGlyph = i;
  634. }
  635. }
  636. stream.write(minGlyph);
  637. stream.write(maxGlyph);
  638. // Skip it if we don't have any glyphs to do...
  639. if(maxGlyph >= minGlyph)
  640. {
  641. // Put everything big endian, to be consistent. Do this inplace.
  642. for(i = minGlyph; i <= maxGlyph; i++)
  643. mRemapTable[i] = convertHostToBEndian(mRemapTable[i]);
  644. {
  645. // Compress.
  646. const U32 buffSize = 128 * 1024;
  647. FrameTemp<S32> outBuff(buffSize);
  648. uLongf destLen = buffSize * sizeof(S32);
  649. compress2((Bytef*)(S32*)outBuff, &destLen, (Bytef*)(S32*)&mRemapTable[minGlyph], (maxGlyph-minGlyph+1)*sizeof(S32), 9);
  650. // Write out.
  651. stream.write((U32)destLen);
  652. stream.write(destLen, outBuff);
  653. }
  654. // Put us back to normal.
  655. for(i = minGlyph; i <= maxGlyph; i++)
  656. mRemapTable[i] = convertBEndianToHost(mRemapTable[i]);
  657. }
  658. return (stream.getStatus() == Stream::Ok);
  659. }
  660. void GFont::exportStrip(const char *fileName, U32 padding, U32 kerning)
  661. {
  662. // Figure dimensions of our strip by iterating over all the char infos.
  663. U32 totalHeight = 0;
  664. U32 totalWidth = 0;
  665. S32 heightMin=0, heightMax=0;
  666. for(S32 i=0; i<mCharInfoList.size(); i++)
  667. {
  668. totalWidth += mCharInfoList[i].width + kerning + 2*padding;
  669. heightMin = getMin((S32)heightMin, (S32)getBaseline() - (S32)mCharInfoList[i].yOrigin);
  670. heightMax = getMax((S32)heightMax, (S32)getBaseline() - (S32)mCharInfoList[i].yOrigin + (S32)mCharInfoList[i].height);
  671. }
  672. totalHeight = heightMax - heightMin + 2*padding;
  673. // Make the bitmap.
  674. GBitmap gb(totalWidth, totalHeight, false, mTextureSheets[0].getBitmap()->getFormat());
  675. dMemset(gb.getWritableBits(), 0, sizeof(U8) * totalHeight * totalWidth );
  676. // Ok, copy some rects, taking into account padding, kerning, offset.
  677. U32 curWidth = kerning + padding;
  678. for(S32 i=0; i<mCharInfoList.size(); i++)
  679. {
  680. // Skip invalid stuff.
  681. if(mCharInfoList[i].bitmapIndex == -1 || mCharInfoList[i].height == 0 || mCharInfoList[i].width == 0)
  682. continue;
  683. // Copy the rect.
  684. U32 bitmap = mCharInfoList[i].bitmapIndex;
  685. RectI ri(mCharInfoList[i].xOffset, mCharInfoList[i].yOffset, mCharInfoList[i].width, mCharInfoList[i].height );
  686. Point2I outRi(curWidth, padding + getBaseline() - mCharInfoList[i].yOrigin);
  687. gb.copyRect(mTextureSheets[bitmap].getBitmap(), ri, outRi);
  688. // Advance.
  689. curWidth += mCharInfoList[i].width + kerning + 2*padding;
  690. }
  691. // Write the image!
  692. FileStream fs;
  693. fs.open( fileName, Torque::FS::File::Write );
  694. if(fs.getStatus() != Stream::Ok)
  695. {
  696. Con::errorf("GFont::exportStrip - failed to open '%s' for writing.", fileName);
  697. return;
  698. }
  699. // Done!
  700. gb.writeBitmap("png", fs);
  701. }
  702. void GFont::setPlatformFont(PlatformFont *inPlatformFont)
  703. {
  704. AssertFatal( mPlatformFont == NULL, "Trying to set platform font which already exists");
  705. mPlatformFont = inPlatformFont;
  706. }
  707. void GFont::importStrip(const char *fileName, U32 padding, U32 kerning)
  708. {
  709. // Wipe our texture sheets, and reload bitmap data from the specified file.
  710. // Also deal with kerning.
  711. // Also, we may have to load RGBA instead of RGB.
  712. // Wipe our texture sheets.
  713. mCurSheet = mCurX = mCurY = 0;
  714. mTextureSheets.clear();
  715. // Now, load the font strip.
  716. Resource<GBitmap> strip = GBitmap::load(fileName);
  717. if(!strip)
  718. {
  719. Con::errorf("GFont::importStrip - could not load file '%s'!", fileName);
  720. return;
  721. }
  722. // And get parsing and copying - load up all the characters as separate
  723. // GBitmaps, sort, then pack. Not terribly efficient but this is basically
  724. // on offline task anyway.
  725. // Ok, snag some glyphs.
  726. Vector<GlyphMap> glyphList;
  727. glyphList.reserve(mCharInfoList.size());
  728. U32 curWidth = 0;
  729. for(S32 i=0; i<mCharInfoList.size(); i++)
  730. {
  731. // Skip invalid stuff.
  732. if(mCharInfoList[i].bitmapIndex == -1 || mCharInfoList[i].height == 0 || mCharInfoList[i].width == 0)
  733. continue;
  734. // Allocate a new bitmap for this glyph, taking into account kerning and padding.
  735. glyphList.increment();
  736. GlyphMap& lastGlyphMap = glyphList.last();
  737. lastGlyphMap.bitmap = new GBitmap(mCharInfoList[i].width + kerning + 2 * padding, mCharInfoList[i].height + 2 * padding, false, strip->getFormat());
  738. lastGlyphMap.charId = i;
  739. // Copy the rect.
  740. RectI ri(curWidth, getBaseline() - mCharInfoList[i].yOrigin, lastGlyphMap.bitmap->getWidth(), lastGlyphMap.bitmap->getHeight());
  741. Point2I outRi(0,0);
  742. lastGlyphMap.bitmap->copyRect(strip, ri, outRi);
  743. // Update glyph attributes.
  744. mCharInfoList[i].width = lastGlyphMap.bitmap->getWidth();
  745. mCharInfoList[i].height = lastGlyphMap.bitmap->getHeight();
  746. mCharInfoList[i].xOffset -= kerning + padding;
  747. mCharInfoList[i].xIncrement += kerning;
  748. mCharInfoList[i].yOffset -= padding;
  749. // Advance.
  750. curWidth += ri.extent.x;
  751. }
  752. // Ok, we have a big list of glyphmaps now. So let's sort them, then pack them.
  753. dQsort(glyphList.address(), glyphList.size(), sizeof(GlyphMap), GlyphMapCompare);
  754. // They're sorted by height, so now we can do some sort of awesome packing.
  755. Point2I curSheetSize(256, 256);
  756. Vector<U32> sheetSizes;
  757. S32 curY = 0;
  758. S32 curX = 0;
  759. S32 curLnHeight = 0;
  760. S32 maxHeight = 0;
  761. for(U32 i = 0; i < glyphList.size(); i++)
  762. {
  763. PlatformFont::CharInfo *ci = &mCharInfoList[glyphList[i].charId];
  764. if(ci->height > maxHeight)
  765. maxHeight = ci->height;
  766. if(curX + ci->width > curSheetSize.x)
  767. {
  768. curY += curLnHeight;
  769. curX = 0;
  770. curLnHeight = 0;
  771. }
  772. if(curY + ci->height > curSheetSize.y)
  773. {
  774. sheetSizes.push_back(curSheetSize.y);
  775. curX = 0;
  776. curY = 0;
  777. curLnHeight = 0;
  778. }
  779. if(ci->height > curLnHeight)
  780. curLnHeight = ci->height;
  781. ci->bitmapIndex = sheetSizes.size();
  782. ci->xOffset = curX;
  783. ci->yOffset = curY;
  784. curX += ci->width;
  785. }
  786. // Terminate the packing loop calculations.
  787. curY += curLnHeight;
  788. if(curY < 64)
  789. curSheetSize.y = 64;
  790. else if(curY < 128)
  791. curSheetSize.y = 128;
  792. sheetSizes.push_back(curSheetSize.y);
  793. if(getHeight() + padding * 2 > maxHeight)
  794. maxHeight = getHeight() + padding * 2;
  795. // Allocate texture pages.
  796. for(S32 i=0; i<sheetSizes.size(); i++)
  797. {
  798. GBitmap *bitmap = new GBitmap(TextureSheetSize, TextureSheetSize, false, strip->getFormat());
  799. // Set everything to transparent.
  800. U8 *bits = bitmap->getWritableBits();
  801. dMemset(bits, 0, sizeof(U8) *TextureSheetSize*TextureSheetSize * strip->getBytesPerPixel());
  802. GFXTexHandle handle = GFXTexHandle( bitmap, &GFXFontTextureProfile, true, avar("%s() - Font Sheet for %s (line %d)", __FUNCTION__, fileName, __LINE__) );
  803. mTextureSheets.increment();
  804. mTextureSheets.last() = handle;
  805. }
  806. // Alright, we're ready to copy bits!
  807. for(S32 i=0; i<glyphList.size(); i++)
  808. {
  809. // Copy each glyph into the appropriate place.
  810. PlatformFont::CharInfo *ci = &mCharInfoList[glyphList[i].charId];
  811. U32 bi = ci->bitmapIndex;
  812. mTextureSheets[bi]->getBitmap()->copyRect(glyphList[i].bitmap, RectI(0,0, glyphList[i].bitmap->getWidth(),glyphList[i].bitmap->getHeight()), Point2I(ci->xOffset, ci->yOffset));
  813. }
  814. // Ok, all done! Just refresh some textures and we're set.
  815. for(S32 i=0; i<sheetSizes.size(); i++)
  816. mTextureSheets[i].refresh();
  817. }
  818. DefineEngineFunction( populateFontCacheString, void, ( const char *faceName, S32 fontSize, const char *string ),,
  819. "Populate the font cache for the specified font with characters from the specified string.\n"
  820. "@param faceName The name of the font face.\n"
  821. "@param fontSize The size of the font in pixels.\n"
  822. "@param string The string to populate.\n"
  823. "@ingroup Font\n" )
  824. {
  825. Resource<GFont> f = GFont::create(faceName, fontSize, Con::getVariable("$GUI::fontCacheDirectory"));
  826. if(f == NULL)
  827. {
  828. Con::errorf("populateFontCacheString - could not load font '%s %d'", faceName, fontSize);
  829. return;
  830. }
  831. if(!f->hasPlatformFont())
  832. {
  833. Con::errorf("populateFontCacheString - font '%s %d' has no platform font. Cannot generate more characters.", faceName, fontSize);
  834. return;
  835. }
  836. // This has the side effect of generating character info, including the bitmaps.
  837. f->getStrWidthPrecise( string );
  838. }
  839. DefineEngineFunction( populateFontCacheRange, void, ( const char *faceName, S32 fontSize, U32 rangeStart, U32 rangeEnd ),,
  840. "Populate the font cache for the specified font with Unicode code points in the specified range.\n"
  841. "@param faceName The name of the font face.\n"
  842. "@param fontSize The size of the font in pixels.\n"
  843. "@param rangeStart The start Unicode point.\n"
  844. "@param rangeEnd The end Unicode point.\n"
  845. "@note We only support BMP-0, so code points range from 0 to 65535.\n"
  846. "@ingroup Font\n" )
  847. {
  848. Resource<GFont> f = GFont::create(faceName, fontSize, Con::getVariable("$GUI::fontCacheDirectory"));
  849. if(f == NULL)
  850. {
  851. Con::errorf("populateFontCacheRange - could not load font '%s %d'", faceName, fontSize);
  852. return;
  853. }
  854. if(rangeStart > rangeEnd)
  855. {
  856. Con::errorf("populateFontCacheRange - range start is after end");
  857. return;
  858. }
  859. if(!f->hasPlatformFont())
  860. {
  861. Con::errorf("populateFontCacheRange - font '%s %d' has no platform font Cannot generate more characters.", faceName, fontSize);
  862. return;
  863. }
  864. // This has the side effect of generating character info, including the bitmaps.
  865. for(U32 i=rangeStart; i<rangeEnd; i++)
  866. {
  867. if(f->isValidChar(i))
  868. f->getCharWidth(i);
  869. else
  870. Con::warnf("populateFontCacheRange - skipping invalid char 0x%x", i);
  871. }
  872. // All done!
  873. }
  874. DefineEngineFunction( dumpFontCacheStatus, void, (),,
  875. "Dumps to the console a full description of all cached fonts, along with "
  876. "info on the codepoints each contains.\n"
  877. "@ingroup Font\n" )
  878. {
  879. Resource<GFont> theFont = ResourceManager::get().startResourceList( Resource<GFont>::signature() );
  880. Con::printf("--------------------------------------------------------------------------");
  881. Con::printf(" Font Cache Usage Report");
  882. while( theFont != NULL )
  883. {
  884. theFont->dumpInfo();
  885. theFont = ResourceManager::get().nextResource();
  886. }
  887. }
  888. DefineEngineFunction( writeFontCache, void, (),,
  889. "Force all cached fonts to serialize themselves to the cache.\n"
  890. "@ingroup Font\n" )
  891. {
  892. Resource<GFont> theFont = ResourceManager::get().startResourceList( Resource<GFont>::signature() );
  893. Con::printf("--------------------------------------------------------------------------");
  894. Con::printf(" Writing font cache to disk");
  895. while( theFont != NULL )
  896. {
  897. const String fileName( theFont.getPath() );
  898. FileStream stream;
  899. stream.open(fileName, Torque::FS::File::Write);
  900. if(stream.getStatus() == Stream::Ok)
  901. {
  902. Con::printf(" o Writing '%s' to disk...", fileName.c_str());
  903. theFont->write(stream);
  904. }
  905. else
  906. {
  907. Con::errorf(" o Could not open '%s' for write", fileName.c_str());
  908. }
  909. theFont = ResourceManager::get().nextResource();
  910. }
  911. }
  912. DefineEngineFunction( populateAllFontCacheString, void, ( const char *string ),,
  913. "Populate the font cache for all fonts with characters from the specified string.\n"
  914. "@ingroup Font\n" )
  915. {
  916. Resource<GFont> theFont = ResourceManager::get().startResourceList( Resource<GFont>::signature() );
  917. Con::printf("Populating font cache with string '%s'", string);
  918. while( theFont != NULL )
  919. {
  920. if(theFont->hasPlatformFont())
  921. {
  922. // This has the side effect of generating character info, including the bitmaps.
  923. theFont->getStrWidthPrecise( string );
  924. }
  925. else
  926. {
  927. const String fileName( theFont.getPath() );
  928. Con::errorf("populateAllFontCacheString - font '%s' has no platform font. Cannot generate more characters.", fileName.c_str());
  929. }
  930. theFont = ResourceManager::get().nextResource();
  931. }
  932. }
  933. DefineEngineFunction( populateAllFontCacheRange, void, ( U32 rangeStart, U32 rangeEnd ),,
  934. "Populate the font cache for all fonts with Unicode code points in the specified range.\n"
  935. "@param rangeStart The start Unicode point.\n"
  936. "@param rangeEnd The end Unicode point.\n"
  937. "@note We only support BMP-0, so code points range from 0 to 65535.\n"
  938. "@ingroup Font\n" )
  939. {
  940. if(rangeStart > rangeEnd)
  941. {
  942. Con::errorf("populateAllFontCacheRange - range start is after end!");
  943. return;
  944. }
  945. Resource<GFont> theFont = ResourceManager::get().startResourceList( Resource<GFont>::signature() );
  946. Con::printf("Populating font cache with range 0x%x to 0x%x", rangeStart, rangeEnd);
  947. while( theFont != NULL )
  948. {
  949. const String fileName( theFont.getPath() );
  950. if(theFont->hasPlatformFont())
  951. {
  952. // This has the side effect of generating character info, including the bitmaps.
  953. Con::printf(" o Populating font '%s'", fileName.c_str());
  954. for(U32 i=rangeStart; i<rangeEnd; i++)
  955. {
  956. if(theFont->isValidChar(i))
  957. theFont->getCharWidth(i);
  958. else
  959. Con::warnf("populateAllFontCacheRange - skipping invalid char 0x%x", i);
  960. }
  961. }
  962. else
  963. {
  964. Con::errorf("populateAllFontCacheRange - font '%s' has no platform font. Cannot generate more characters.", fileName.c_str());
  965. }
  966. theFont = ResourceManager::get().nextResource();
  967. }
  968. }
  969. DefineEngineFunction( exportCachedFont, void,
  970. ( const char *faceName, S32 fontSize, const char *fileName, S32 padding, S32 kerning ),,
  971. "Export specified font to the specified filename as a PNG. The "
  972. "image can then be processed in Photoshop or another tool and "
  973. "reimported using importCachedFont. Characters in the font are "
  974. "exported as one long strip.\n"
  975. "@param faceName The name of the font face.\n"
  976. "@param fontSize The size of the font in pixels.\n"
  977. "@param fileName The file name and path for the output PNG.\n"
  978. "@param padding The padding between characters.\n"
  979. "@param kerning The kerning between characters.\n"
  980. "@ingroup Font\n" )
  981. {
  982. // Tell the font to export itself.
  983. Resource<GFont> f = GFont::create(faceName, fontSize, Con::getVariable("$GUI::fontCacheDirectory"));
  984. if(f == NULL)
  985. {
  986. Con::errorf("exportCachedFont - could not load font '%s %d'", faceName, fontSize);
  987. return;
  988. }
  989. f->exportStrip(fileName, padding, kerning);
  990. }
  991. DefineEngineFunction( importCachedFont, void,
  992. ( const char *faceName, S32 fontSize, const char *fileName, S32 padding, S32 kerning ),,
  993. "Import an image strip from exportCachedFont. Call with the "
  994. "same parameters you called exportCachedFont.\n"
  995. "@param faceName The name of the font face.\n"
  996. "@param fontSize The size of the font in pixels.\n"
  997. "@param fileName The file name and path for the input PNG.\n"
  998. "@param padding The padding between characters.\n"
  999. "@param kerning The kerning between characters.\n"
  1000. "@ingroup Font\n" )
  1001. {
  1002. // Tell the font to import itself.
  1003. Resource<GFont> f = GFont::create(faceName, fontSize, Con::getVariable("$GUI::fontCacheDirectory"));
  1004. if(f == NULL)
  1005. {
  1006. Con::errorf("importCachedFont - could not load font '%s %d'", faceName, fontSize);
  1007. return;
  1008. }
  1009. f->importStrip(fileName, padding, kerning);
  1010. }
  1011. DefineEngineFunction( duplicateCachedFont, void,
  1012. ( const char *oldFontName, S32 oldFontSize, const char *newFontName ),,
  1013. "Copy the specified old font to a new name. The new copy will not have a "
  1014. "platform font backing it, and so will never have characters added to it. "
  1015. "But this is useful for making copies of fonts to add postprocessing effects "
  1016. "to via exportCachedFont.\n"
  1017. "@param oldFontName The name of the font face to copy.\n"
  1018. "@param oldFontSize The size of the font to copy.\n"
  1019. "@param newFontName The name of the new font face.\n"
  1020. "@ingroup Font\n" )
  1021. {
  1022. String newFontFile = GFont::getFontCacheFilename(newFontName, oldFontSize);
  1023. // Load the original font.
  1024. Resource<GFont> font = GFont::create(oldFontName, oldFontSize, Con::getVariable("$GUI::fontCacheDirectory"));
  1025. // Deal with inexplicably missing or failed to load fonts.
  1026. if (font == NULL)
  1027. {
  1028. Con::errorf(" o Couldn't find font : %s", oldFontName);
  1029. return;
  1030. }
  1031. // Ok, dump info!
  1032. FileStream stream;
  1033. stream.open( newFontFile, Torque::FS::File::Write );
  1034. if(stream.getStatus() == Stream::Ok)
  1035. {
  1036. Con::printf( " o Writing duplicate font '%s' to disk...", newFontFile.c_str() );
  1037. font->write(stream);
  1038. stream.close();
  1039. }
  1040. else
  1041. {
  1042. Con::errorf( " o Could not open '%s' for write", newFontFile.c_str() );
  1043. }
  1044. }