gFont.cpp 38 KB

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